PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.1
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.1
5.13.0 5.12.1 5.12.0 5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Period / Range.php
matomo / app / core / Period Last commit date
Day.php 2 years ago Factory.php 2 years ago Month.php 2 years ago PeriodValidator.php 2 years ago Range.php 2 years ago Week.php 2 years ago Year.php 2 years ago
Range.php
467 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 *
9 */
10 namespace Piwik\Period;
11
12 use Exception;
13 use Piwik\Cache;
14 use Piwik\Common;
15 use Piwik\Container\StaticContainer;
16 use Piwik\Date;
17 use Piwik\Period;
18 /**
19 * Arbitrary date range representation.
20 *
21 * This class represents a period that contains a list of consecutive days as subperiods
22 * It is created when the **period** query parameter is set to **range** and is used
23 * to calculate the subperiods of multiple period requests (eg, when period=day and
24 * date=2007-07-24,2013-11-15).
25 *
26 * The range period differs from other periods mainly in that since it is arbitrary,
27 * range periods are not pre-archived by the cron core:archive command.
28 *
29 * @api
30 */
31 class Range extends Period
32 {
33 const PERIOD_ID = 5;
34 protected $label = 'range';
35 protected $today;
36 /**
37 * @var null|Date
38 */
39 protected $defaultEndDate;
40 protected $strPeriod;
41 protected $strDate;
42 protected $timezone;
43 /**
44 * Constructor.
45 *
46 * @param string $strPeriod The type of period each subperiod is. Either `'day'`, `'week'`,
47 * `'month'` or `'year'`.
48 * @param string $strDate The date range, eg, `'2007-07-24,2013-11-15'`.
49 * @param string $timezone The timezone to use, eg, `'UTC'`.
50 * @param bool|Date $today The date to use as _today_. Defaults to `Date::factory('today', $timzeone)`.
51 * @api
52 */
53 public function __construct($strPeriod, $strDate, $timezone = 'UTC', $today = false)
54 {
55 $this->strPeriod = $strPeriod;
56 $this->strDate = $strDate;
57 $this->timezone = $timezone;
58 $this->defaultEndDate = null;
59 if ($today === false) {
60 $today = Date::factory('now', $this->timezone);
61 }
62 $this->today = $today;
63 $this->translator = StaticContainer::get('Piwik\\Translation\\Translator');
64 }
65 public function __sleep()
66 {
67 return ['strPeriod', 'strDate', 'timezone', 'defaultEndDate', 'today'];
68 }
69 public function __wakeup()
70 {
71 $this->translator = StaticContainer::get('Piwik\\Translation\\Translator');
72 }
73 private function getCache()
74 {
75 return Cache::getTransientCache();
76 }
77 private function getCacheId()
78 {
79 $end = '';
80 if ($this->defaultEndDate) {
81 $end = $this->defaultEndDate->getTimestamp();
82 }
83 $today = $this->today->getTimestamp();
84 return 'range' . $this->strPeriod . $this->strDate . $this->timezone . $end . $today;
85 }
86 private function loadAllFromCache()
87 {
88 $range = $this->getCache()->fetch($this->getCacheId());
89 if (!empty($range)) {
90 foreach ($range as $key => $val) {
91 $this->{$key} = $val;
92 }
93 }
94 }
95 private function cacheAll()
96 {
97 $props = get_object_vars($this);
98 $this->getCache()->save($this->getCacheId(), $props);
99 }
100 /**
101 * Returns the current period as a localized short string.
102 *
103 * @return string
104 */
105 public function getLocalizedShortString()
106 {
107 return $this->getTranslatedRange($this->getRangeFormat(true));
108 }
109 /**
110 * Returns the current period as a localized long string.
111 *
112 * @return string
113 */
114 public function getLocalizedLongString()
115 {
116 return $this->getTranslatedRange($this->getRangeFormat());
117 }
118 /**
119 * Returns the start date of the period.
120 *
121 * @return Date
122 * @throws Exception
123 */
124 public function getDateStart()
125 {
126 $dateStart = parent::getDateStart();
127 if (empty($dateStart)) {
128 throw new Exception("Specified date range is invalid.");
129 }
130 return $dateStart;
131 }
132 /**
133 * Returns the current period as a string.
134 *
135 * @return string
136 */
137 public function getPrettyString()
138 {
139 $out = $this->translator->translate('General_DateRangeFromTo', array($this->getDateStart()->toString(), $this->getDateEnd()->toString()));
140 return $out;
141 }
142 protected function getMaxN(int $lastN) : int
143 {
144 switch ($this->strPeriod) {
145 case 'day':
146 $lastN = min($lastN, 5 * 365);
147 break;
148 case 'week':
149 $lastN = min($lastN, 10 * 52);
150 break;
151 case 'month':
152 $lastN = min($lastN, 10 * 12);
153 break;
154 case 'year':
155 $lastN = min($lastN, 10);
156 break;
157 }
158 return $lastN;
159 }
160 /**
161 * Sets the default end date of the period.
162 *
163 * @param Date $oDate
164 */
165 public function setDefaultEndDate(Date $oDate)
166 {
167 $this->defaultEndDate = $oDate;
168 }
169 /**
170 * Generates the subperiods
171 *
172 * @throws Exception
173 */
174 protected function generate()
175 {
176 if ($this->subperiodsProcessed) {
177 return;
178 }
179 $this->loadAllFromCache();
180 if ($this->subperiodsProcessed) {
181 return;
182 }
183 parent::generate();
184 if (preg_match('/^(last|previous)([0-9]*)$/', $this->strDate, $regs)) {
185 $lastN = $regs[2];
186 $lastOrPrevious = $regs[1];
187 if (!is_null($this->defaultEndDate)) {
188 $defaultEndDate = $this->defaultEndDate;
189 } else {
190 $defaultEndDate = $this->today;
191 }
192 $period = $this->strPeriod;
193 if ($period == 'range') {
194 $period = 'day';
195 }
196 if ($lastOrPrevious == 'last') {
197 $endDate = $defaultEndDate;
198 } elseif ($lastOrPrevious == 'previous') {
199 if ('month' == $period) {
200 $endDate = $defaultEndDate->subMonth(1);
201 } else {
202 $endDate = $defaultEndDate->subPeriod(1, $period);
203 }
204 }
205 $lastN = $this->getMaxN((int) $lastN);
206 // last1 means only one result ; last2 means 2 results so we remove only 1 to the days/weeks/etc
207 $lastN--;
208 if ($lastN < 0) {
209 $lastN = 0;
210 }
211 $startDate = $endDate->addPeriod(-1 * $lastN, $period);
212 } elseif ($dateRange = \Piwik\Period\Range::parseDateRange($this->strDate)) {
213 $strDateStart = $dateRange[1];
214 $strDateEnd = $dateRange[2];
215 $startDate = Date::factory($strDateStart);
216 // we set the timezone in the Date object only if the date is relative eg. 'today', 'yesterday', 'now'
217 $timezone = null;
218 if (strpos($strDateEnd, '-') === false) {
219 $timezone = $this->timezone;
220 }
221 $endDate = Date::factory($strDateEnd, $timezone)->setTime("00:00:00");
222 } else {
223 throw new Exception($this->translator->translate('General_ExceptionInvalidDateRange', array($this->strDate, ' \'lastN\', \'previousN\', \'YYYY-MM-DD,YYYY-MM-DD\'')));
224 }
225 if ($this->strPeriod != 'range') {
226 $this->fillArraySubPeriods($startDate, $endDate, $this->strPeriod);
227 $this->cacheAll();
228 return;
229 }
230 $this->processOptimalSubperiods($startDate, $endDate);
231 // When period=range, we want End Date to be the actual specified end date,
232 // rather than the end of the month / week / whatever is used for processing this range
233 $this->endDate = $endDate;
234 $this->cacheAll();
235 }
236 /**
237 * Given a date string, returns `false` if not a date range,
238 * or returns the array containing start and end dates.
239 *
240 * @param string $dateString
241 * @return mixed array(1 => dateStartString, 2 => dateEndString) or `false` if the input was not a date range.
242 */
243 public static function parseDateRange($dateString)
244 {
245 $matched = preg_match('/^((?:[0-9]{4}-[0-9]{1,2}-[0-9]{1,2})|last[ -]?(?:week|month|year)),((?:[0-9]{4}-[0-9]{1,2}-[0-9]{1,2})|today|now|yesterday|last[ -]?(?:week|month|year))$/Di', trim($dateString), $regs);
246 if (empty($matched)) {
247 return false;
248 }
249 return $regs;
250 }
251 protected $endDate = null;
252 /**
253 * Returns the end date of the period.
254 *
255 * @return null|Date
256 */
257 public function getDateEnd()
258 {
259 if (!is_null($this->endDate)) {
260 return $this->endDate;
261 }
262 return parent::getDateEnd();
263 }
264 /**
265 * Determine which kind of period is best to use.
266 * See Range.test.php
267 *
268 * @param Date $startDate
269 * @param Date $endDate
270 */
271 protected function processOptimalSubperiods($startDate, $endDate)
272 {
273 while ($startDate->isEarlier($endDate) || $startDate == $endDate) {
274 $endOfPeriod = null;
275 $month = new \Piwik\Period\Month($startDate);
276 $endOfMonth = $month->getDateEnd();
277 $startOfMonth = $month->getDateStart();
278 $year = new \Piwik\Period\Year($startDate);
279 $endOfYear = $year->getDateEnd();
280 $startOfYear = $year->getDateStart();
281 if ($startDate == $startOfYear && ($endOfYear->isEarlier($endDate) || $endOfYear == $endDate || $endOfYear->isLater($this->today)) && !($endDate->isEarlier($this->today) && $this->today->toString('Y') == $endOfYear->toString('Y') && $this->today->compareYear($endOfYear) == 0)) {
282 $this->addSubperiod($year);
283 $endOfPeriod = $endOfYear;
284 } elseif ($startDate == $startOfMonth && ($endOfMonth->isEarlier($endDate) || $endOfMonth == $endDate || $endOfMonth->isLater($this->today)) && !($endDate->isEarlier($this->today) && $this->today->toString('Y') == $endOfMonth->toString('Y') && $this->today->compareMonth($endOfMonth) == 0)) {
285 $this->addSubperiod($month);
286 $endOfPeriod = $endOfMonth;
287 } else {
288 // From start date,
289 // Process end of week
290 $week = new \Piwik\Period\Week($startDate);
291 $startOfWeek = $week->getDateStart();
292 $endOfWeek = $week->getDateEnd();
293 $firstDayNextMonth = $startDate->addPeriod(2, 'month')->setDay(1);
294 $useMonthsNextIteration = $firstDayNextMonth->isEarlier($endDate);
295 if ($useMonthsNextIteration && $endOfWeek->isLater($endOfMonth)) {
296 $this->fillArraySubPeriods($startDate, $endOfMonth, 'day');
297 $endOfPeriod = $endOfMonth;
298 } elseif ($this->isEndOfWeekLaterThanEndDate($endDate, $endOfWeek) && ($endOfWeek->isEarlier($this->today) || $startOfWeek->toString() != $startDate->toString() || $endDate->isEarlier($this->today))) {
299 $this->fillArraySubPeriods($startDate, $endDate, 'day');
300 break 1;
301 } elseif ($startOfWeek->isEarlier($startDate) && $endOfWeek->isEarlier($this->today)) {
302 $this->fillArraySubPeriods($startDate, $endOfWeek, 'day');
303 $endOfPeriod = $endOfWeek;
304 } else {
305 $this->addSubperiod($week);
306 $endOfPeriod = $endOfWeek;
307 }
308 }
309 $startDate = $endOfPeriod->addDay(1);
310 }
311 }
312 /**
313 * Adds new subperiods
314 *
315 * @param Date $startDate
316 * @param Date $endDate
317 * @param string $period
318 */
319 protected function fillArraySubPeriods($startDate, $endDate, $period)
320 {
321 $arrayPeriods = array();
322 $endSubperiod = Period\Factory::build($period, $endDate);
323 $arrayPeriods[] = $endSubperiod;
324 // set end date to start of end period since we're comparing against start date.
325 $endDate = $endSubperiod->getDateStart();
326 while ($endDate->isLater($startDate)) {
327 $endDate = $endDate->addPeriod(-1, $period);
328 $subPeriod = Period\Factory::build($period, $endDate);
329 $arrayPeriods[] = $subPeriod;
330 }
331 $arrayPeriods = array_reverse($arrayPeriods);
332 foreach ($arrayPeriods as $period) {
333 $this->addSubperiod($period);
334 }
335 }
336 /**
337 * Returns the date that is one period before the supplied date.
338 *
339 * @param bool|string $date The date to get the last date of.
340 * @param bool|string $period The period to use (either 'day', 'week', 'month', 'year');
341 *
342 * @return array An array with two elements, a string for the date before $date and
343 * a Period instance for the period before $date.
344 * @api
345 */
346 public static function getLastDate($date = false, $period = false)
347 {
348 return self::getDateXPeriodsAgo(1, $date, $period);
349 }
350 /**
351 * Returns the date that is X periods before the supplied date.
352 *
353 * @param bool|string $date The date to get the last date of.
354 * @param bool|string $period The period to use (either 'day', 'week', 'month', 'year');
355 * @param int $subXPeriods How many periods in the past the date should be, for instance 1 or 7.
356 * If sub period is 365 days and the current year is a leap year we assume you want to get the
357 * day one year ago and change the value to 366 days therefore.
358 *
359 * @return array An array with two elements, a string for the date before $date and
360 * a Period instance for the period before $date.
361 * @api
362 */
363 public static function getDateXPeriodsAgo($subXPeriods, $date = false, $period = false)
364 {
365 if ($date === false) {
366 $date = Common::getRequestVar('date');
367 }
368 if ($period === false) {
369 $period = Common::getRequestVar('period');
370 }
371 if (365 == $subXPeriods && 'day' == $period && Date::factory($date)->isLeapYear()) {
372 $subXPeriods = 366;
373 }
374 if ($period === 'range') {
375 $rangePeriod = new \Piwik\Period\Range($period, $date);
376 $daysDifference = self::getNumDaysDifference($rangePeriod->getDateStart(), $rangePeriod->getDateEnd());
377 $end = $rangePeriod->getDateStart()->subDay(1);
378 $from = $end->subDay($daysDifference);
379 return array("{$from},{$end}", false);
380 }
381 // can't get the last date for range periods & dates that use lastN/previousN
382 $strLastDate = false;
383 $lastPeriod = false;
384 if (!preg_match('/(last|previous)([0-9]*)/', $date, $regs)) {
385 if (strpos($date, ',')) {
386 // date in the form of 2011-01-01,2011-02-02
387 $rangePeriod = new \Piwik\Period\Range($period, $date);
388 $lastStartDate = $rangePeriod->getDateStart()->subPeriod($subXPeriods, $period);
389 $lastEndDate = $rangePeriod->getDateEnd()->subPeriod($subXPeriods, $period);
390 $strLastDate = "{$lastStartDate},{$lastEndDate}";
391 } else {
392 $lastPeriod = Date::factory($date)->subPeriod($subXPeriods, $period);
393 $strLastDate = $lastPeriod->toString();
394 }
395 }
396 return array($strLastDate, $lastPeriod);
397 }
398 /**
399 * Return the number of days contained in this range
400 *
401 * @return int
402 * @throws Exception
403 */
404 public function getDayCount()
405 {
406 return self::getNumDaysDifference($this->getDateStart(), $this->getDateEnd()) + 1;
407 }
408 private static function getNumDaysDifference(Date $date1, Date $date2)
409 {
410 $days = abs($date1->getTimestamp() - $date2->getTimestamp()) / 60 / 60 / 24;
411 return (int) round($days);
412 }
413 /**
414 * Returns a date range string given a period type, end date and number of periods
415 * the range spans over.
416 *
417 * @param string $period The sub period type, `'day'`, `'week'`, `'month'` and `'year'`.
418 * @param int $lastN The number of periods of type `$period` that the result range should
419 * span.
420 * @param string $endDate The desired end date of the range.
421 * @param \Piwik\Site $site The site whose timezone should be used.
422 * @return string The date range string, eg, `'2012-01-02,2013-01-02'`.
423 * @api
424 */
425 public static function getRelativeToEndDate($period, $lastN, $endDate, $site)
426 {
427 $timezone = $site->getTimezone();
428 $last30Relative = new \Piwik\Period\Range($period, $lastN, $timezone);
429 if (strpos($endDate, '-') === false) {
430 // eg today, yesterday, ... needs the timezone
431 $endDate = Date::factoryInTimezone($endDate, $timezone);
432 } else {
433 $endDate = Date::factory($endDate);
434 }
435 $last30Relative->setDefaultEndDate($endDate);
436 $date = $last30Relative->getDateStart()->toString() . "," . $last30Relative->getDateEnd()->toString();
437 return $date;
438 }
439 private function isEndOfWeekLaterThanEndDate($endDate, $endOfWeek)
440 {
441 $isEndOfWeekLaterThanEndDate = $endOfWeek->isLater($endDate);
442 $isEndDateAlsoEndOfWeek = $endOfWeek->toString() == $endDate->toString();
443 $isEndOfWeekLaterThanEndDate = $isEndOfWeekLaterThanEndDate || $isEndDateAlsoEndOfWeek && $endDate->isLater($this->today);
444 return $isEndOfWeekLaterThanEndDate;
445 }
446 /**
447 * Returns the date range string comprising two dates
448 *
449 * @return string eg, `'2012-01-01,2012-01-31'`.
450 */
451 public function getRangeString()
452 {
453 $dateStart = $this->getDateStart();
454 $dateEnd = $this->getDateEnd();
455 return $dateStart->toString("Y-m-d") . "," . $dateEnd->toString("Y-m-d");
456 }
457 public function getImmediateChildPeriodLabel()
458 {
459 $subperiods = $this->getSubperiods();
460 return reset($subperiods)->getImmediateChildPeriodLabel();
461 }
462 public function getParentPeriodLabel()
463 {
464 return null;
465 }
466 }
467