PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.6.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.6.0
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 / Date.php
matomo / app / core Last commit date
API 7 months ago Access 7 months ago Application 1 year ago Archive 7 months ago ArchiveProcessor 7 months ago Archiver 2 years ago AssetManager 7 months ago Auth 7 months ago Category 7 months ago Changes 7 months ago CliMulti 1 year ago Columns 7 months ago Concurrency 7 months ago Config 7 months ago Container 7 months ago CronArchive 7 months ago DataAccess 7 months ago DataFiles 2 years ago DataTable 7 months ago Db 7 months ago DeviceDetector 1 year ago Email 2 years ago Exception 1 year ago Http 1 year ago Intl 7 months ago Log 2 years ago Mail 1 year ago Measurable 7 months ago Menu 7 months ago Metrics 7 months ago Notification 7 months ago Period 7 months ago Plugin 7 months ago Policy 7 months ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 1 year ago Request 7 months ago Scheduler 7 months ago Segment 7 months ago Session 7 months ago Settings 7 months ago Tracker 7 months ago Translation 1 year ago Twig 1 year ago UpdateCheck 7 months ago Updater 7 months ago Updates 7 months ago Validators 1 year ago View 7 months ago ViewDataTable 7 months ago Visualization 1 year ago Widget 1 year ago .htaccess 2 years ago Access.php 7 months ago Archive.php 7 months ago ArchiveProcessor.php 7 months ago AssetManager.php 1 year ago Auth.php 7 months ago AuthResult.php 7 months ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 1 year ago CliMulti.php 7 months ago Common.php 7 months ago Config.php 1 year ago Console.php 1 year ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 7 months ago DI.php 1 year ago DataArray.php 1 year ago DataTable.php 7 months ago Date.php 7 months ago Db.php 1 year ago DbHelper.php 7 months ago Development.php 1 year ago ErrorHandler.php 7 months ago EventDispatcher.php 1 year ago ExceptionHandler.php 7 months ago FileIntegrity.php 1 year ago Filechecks.php 1 year ago Filesystem.php 1 year ago FrontController.php 7 months ago Http.php 7 months ago IP.php 1 year ago Log.php 7 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 7 months ago NoAccessException.php 2 years ago Nonce.php 7 months ago Notification.php 7 months ago NumberFormatter.php 1 year ago Option.php 1 year ago Period.php 1 year ago Piwik.php 7 months ago Plugin.php 1 year ago Process.php 1 year ago Profiler.php 7 months ago ProxyHeaders.php 2 years ago ProxyHttp.php 1 year ago QuickForm2.php 1 year ago RankingQuery.php 7 months ago ReportRenderer.php 1 year ago Request.php 1 year ago Segment.php 7 months ago Sequence.php 7 months ago Session.php 1 year ago SettingsPiwik.php 1 year ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 7 months ago SiteContentDetector.php 1 year ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 2 years ago Tracker.php 1 year ago Twig.php 7 months ago Unzip.php 1 year ago UpdateCheck.php 1 year ago Updater.php 1 year ago UpdaterErrorException.php 2 years ago Updates.php 1 year ago Url.php 7 months ago UrlHelper.php 1 year ago Version.php 7 months ago View.php 7 months ago bootstrap.php 1 year ago dispatch.php 2 years ago testMinimumPhpVersion.php 7 months ago
Date.php
1020 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik;
10
11 use Exception;
12 use Piwik\Container\StaticContainer;
13 use Piwik\Intl\Data\Provider\DateTimeFormatProvider;
14 /**
15 * Utility class that wraps date/time related PHP functions. Using this class can
16 * be easier than using `date`, `time`, `date_default_timezone_set`, etc.
17 *
18 * ### Performance concerns
19 *
20 * The helper methods in this class are instance methods and thus `Date` instances
21 * need to be constructed before they can be used. The memory allocation can result
22 * in noticeable performance degradation if you construct thousands of Date instances,
23 * say, in a loop.
24 *
25 * ### Examples
26 *
27 * **Basic usage**
28 *
29 * $date = Date::factory('2007-07-24 14:04:24', 'EST');
30 * $date->addHour(5);
31 * echo $date->getLocalized("EEE, d. MMM y 'at' HH:mm:ss");
32 *
33 * @api
34 */
35 class Date
36 {
37 /** Number of seconds in a day. */
38 public const NUM_SECONDS_IN_DAY = 86400;
39 /** The default date time string format. */
40 public const DATE_TIME_FORMAT = 'Y-m-d H:i:s';
41 /** Timestamp when first website came online - Tue, 06 Aug 1991 00:00:00 GMT. */
42 public const FIRST_WEBSITE_TIMESTAMP = 681436800;
43 public const DATETIME_FORMAT_LONG = DateTimeFormatProvider::DATE_FORMAT_LONG;
44 public const DATETIME_FORMAT_SHORT = DateTimeFormatProvider::DATETIME_FORMAT_SHORT;
45 public const DATE_FORMAT_LONG = DateTimeFormatProvider::DATE_FORMAT_LONG;
46 public const DATE_FORMAT_DAY_MONTH = DateTimeFormatProvider::DATE_FORMAT_DAY_MONTH;
47 public const DATE_FORMAT_SHORT = DateTimeFormatProvider::DATE_FORMAT_SHORT;
48 public const DATE_FORMAT_MONTH_SHORT = DateTimeFormatProvider::DATE_FORMAT_MONTH_SHORT;
49 public const DATE_FORMAT_MONTH_LONG = DateTimeFormatProvider::DATE_FORMAT_MONTH_LONG;
50 public const DATE_FORMAT_YEAR = DateTimeFormatProvider::DATE_FORMAT_YEAR;
51 public const TIME_FORMAT = DateTimeFormatProvider::TIME_FORMAT;
52 /** for tests
53 * @var null|int
54 */
55 public static $now = null;
56 /**
57 * Max days for months (non-leap-year). See {@link addPeriod()} implementation.
58 *
59 * @var int[]
60 */
61 private static $maxDaysInMonth = array('1' => 31, '2' => 28, '3' => 31, '4' => 30, '5' => 31, '6' => 30, '7' => 31, '8' => 31, '9' => 30, '10' => 31, '11' => 30, '12' => 31);
62 /**
63 * The stored timestamp is always UTC based.
64 * The returned timestamp via getTimestamp() will have the conversion applied
65 * @var int|null
66 */
67 protected $timestamp = null;
68 /**
69 * Timezone the current date object is set to.
70 * Timezone will only affect the returned timestamp via getTimestamp()
71 * @var string
72 */
73 protected $timezone = 'UTC';
74 /**
75 * Constructor.
76 *
77 * @param int $timestamp The number in seconds since the unix epoch.
78 * @param string $timezone The timezone of the datetime.
79 * @throws Exception If $timestamp is not an int.
80 */
81 protected function __construct($timestamp, $timezone = 'UTC')
82 {
83 if (!is_int($timestamp)) {
84 throw new Exception("Date is expecting a unix timestamp, got: '{$timestamp}'.");
85 }
86 $this->timezone = $timezone;
87 $this->timestamp = $timestamp;
88 }
89 /**
90 * Creates a new Date instance using a string datetime value. The timezone of the Date
91 * result will be in UTC.
92 *
93 * @param string|int $dateString `'today'`, `'yesterday'`, `'now'`, `'yesterdaySameTime'`, a string with
94 * `'YYYY-MM-DD HH:MM:SS'` format or a unix timestamp.
95 * @param string $timezone The timezone of the result. If specified, `$dateString` will be converted
96 * from UTC to this timezone before being used in the Date return value.
97 * @throws Exception If `$dateString` is in an invalid format or if the time is before
98 * Tue, 06 Aug 1991.
99 * @return Date
100 */
101 public static function factory($dateString, $timezone = null)
102 {
103 if ($dateString instanceof self) {
104 return new \Piwik\Date($dateString->timestamp, $dateString->timezone);
105 }
106 if ($dateString === 'now') {
107 $date = self::now();
108 } elseif ($dateString === 'today') {
109 $date = self::today();
110 } elseif ($dateString === 'tomorrow') {
111 $date = self::tomorrow();
112 } elseif ($dateString === 'yesterday') {
113 $date = self::yesterday();
114 } elseif ($dateString === 'yesterdaySameTime') {
115 $date = self::yesterdaySameTime();
116 } elseif (is_string($dateString) && preg_match('/last[ -]?week/i', urldecode($dateString))) {
117 $date = self::lastWeek();
118 } elseif (is_string($dateString) && preg_match('/last[ -]?month/i', urldecode($dateString))) {
119 $date = self::lastMonth();
120 } elseif (is_string($dateString) && preg_match('/last[ -]?year/i', urldecode($dateString))) {
121 $date = self::lastYear();
122 } elseif (!is_int($dateString) && (!is_string($dateString) || strpos($dateString, ',') !== \false || ($dateString = strtotime($dateString)) === \false)) {
123 throw self::getInvalidDateFormatException($dateString);
124 } else {
125 $date = new \Piwik\Date($dateString);
126 }
127 $timestamp = $date->getTimestamp();
128 if ($timestamp < self::FIRST_WEBSITE_TIMESTAMP) {
129 $dateOfFirstWebsite = new self(self::FIRST_WEBSITE_TIMESTAMP);
130 $message = \Piwik\Piwik::translate('General_ExceptionInvalidDateBeforeFirstWebsite', array($date->toString(), $dateOfFirstWebsite->getLocalized(self::DATE_FORMAT_SHORT), $dateOfFirstWebsite->getTimestamp()));
131 throw new Exception($message . ": {$dateString}");
132 }
133 if (empty($timezone)) {
134 return $date;
135 }
136 $timestamp = self::adjustForTimezone($timestamp, $timezone);
137 return \Piwik\Date::factory($timestamp);
138 }
139 /**
140 * Returns Date w/ UTC timestamp of time $dateString/$timezone.
141 * (Only applies to special strings, like 'now','today','yesterday','yesterdaySameTime'.
142 *
143 * @param $dateString
144 * @param $timezone
145 * @return Date
146 * @ignore
147 */
148 public static function factoryInTimezone($dateString, $timezone)
149 {
150 if ($dateString === 'now') {
151 return self::nowInTimezone($timezone);
152 } elseif ($dateString === 'today') {
153 return self::todayInTimezone($timezone);
154 } elseif ($dateString === 'yesterday') {
155 return self::yesterdayInTimezone($timezone);
156 } elseif ($dateString === 'yesterdaySameTime') {
157 return self::yesterdaySameTimeInTimezone($timezone);
158 } elseif (preg_match('/last[ -]?week/i', urldecode($dateString))) {
159 return self::lastWeekInTimezone($timezone);
160 } elseif (preg_match('/last[ -]?month/i', urldecode($dateString))) {
161 return self::lastMonthInTimezone($timezone);
162 } elseif (preg_match('/last[ -]?year/i', urldecode($dateString))) {
163 return self::lastYearInTimezone($timezone);
164 } else {
165 throw new \Exception("Date::factoryInTimezone() should not be used with {$dateString}.");
166 }
167 }
168 private static function nowInTimezone($timezone)
169 {
170 $now = self::getNowTimestamp();
171 $now = self::adjustForTimezone($now, $timezone);
172 return new \Piwik\Date($now);
173 }
174 private static function todayInTimezone($timezone)
175 {
176 return self::nowInTimezone($timezone)->getStartOfDay();
177 }
178 private static function yesterdayInTimezone($timezone)
179 {
180 return self::todayInTimezone($timezone)->subDay(1);
181 }
182 private static function yesterdaySameTimeInTimezone($timezone)
183 {
184 return self::nowInTimezone($timezone)->subDay(1);
185 }
186 private static function lastWeekInTimezone($timezone)
187 {
188 return new \Piwik\Date(strtotime('-1week', self::todayInTimezone($timezone)->getTimestamp()));
189 }
190 private static function lastMonthInTimezone($timezone)
191 {
192 return new \Piwik\Date(strtotime('-1month', self::todayInTimezone($timezone)->getTimestamp()));
193 }
194 private static function lastYearInTimezone($timezone)
195 {
196 return new \Piwik\Date(strtotime('-1year', self::todayInTimezone($timezone)->getTimestamp()));
197 }
198 /**
199 * Returns the current timestamp as a string with the following format: `'YYYY-MM-DD HH:MM:SS'`.
200 *
201 * @return string
202 */
203 public function getDatetime()
204 {
205 return $this->toString(self::DATE_TIME_FORMAT);
206 }
207 /**
208 * Returns the current hour in UTC timezone.
209 * @return string
210 * @throws Exception
211 */
212 public function getHourUTC()
213 {
214 $dateTime = $this->getDatetime();
215 $hourInTz = \Piwik\Date::factory($dateTime, 'UTC')->toString('G');
216 return $hourInTz;
217 }
218 /**
219 * @return string
220 * @deprecated
221 */
222 public function getDateStartUTC()
223 {
224 return $this->getStartOfDay()->toString(self::DATE_TIME_FORMAT);
225 }
226 /**
227 * Returns the start of the day of the current timestamp in UTC. For example,
228 * if the current timestamp is `'2007-07-24 14:04:24'` in UTC, the result will
229 * be `'2007-07-24'` as a Date.
230 *
231 * @return Date
232 */
233 public function getStartOfDay()
234 {
235 $dateStartUTC = gmdate('Y-m-d', $this->timestamp);
236 return \Piwik\Date::factory($dateStartUTC)->setTimezone($this->timezone);
237 }
238 /**
239 * @return string
240 * @deprecated
241 */
242 public function getDateEndUTC()
243 {
244 return $this->getEndOfDay()->toString(self::DATE_TIME_FORMAT);
245 }
246 /**
247 * Returns the end of the day of the current timestamp in UTC. For example,
248 * if the current timestamp is `'2007-07-24 14:03:24'` in UTC, the result will
249 * be `'2007-07-24 23:59:59'`.
250 *
251 * @return Date
252 */
253 public function getEndOfDay()
254 {
255 $dateEndUTC = gmdate('Y-m-d 23:59:59', $this->timestamp);
256 return \Piwik\Date::factory($dateEndUTC)->setTimezone($this->timezone);
257 }
258 /**
259 * Returns a new date object with the same timestamp as `$this` but with a new
260 * timezone.
261 *
262 * See {@link getTimestamp()} to see how the timezone is used.
263 *
264 * @param string $timezone eg, `'UTC'`, `'Europe/London'`, etc.
265 * @return Date
266 */
267 public function setTimezone($timezone)
268 {
269 return new \Piwik\Date($this->timestamp, $timezone);
270 }
271 /**
272 * Returns the offset to UTC time for the given timezone
273 *
274 * @param string $timezone
275 * @return int offset in seconds
276 */
277 public static function getUtcOffset($timezone)
278 {
279 $timestampUTC = self::today()->getTimestampUTC();
280 $timestampZone = self::adjustForTimezone($timestampUTC, $timezone);
281 return $timestampZone - $timestampUTC;
282 }
283 /**
284 * Helper function that returns the offset in the timezone string 'UTC+14'
285 * Returns false if the timezone is not UTC+X or UTC-X
286 *
287 * @param string $timezone
288 * @return int|bool utc offset or false
289 */
290 protected static function extractUtcOffset($timezone)
291 {
292 if ($timezone === 'UTC') {
293 return 0;
294 }
295 $start = substr($timezone, 0, 4);
296 if ($start !== 'UTC-' && $start !== 'UTC+') {
297 return \false;
298 }
299 $offset = (float) substr($timezone, 4);
300 if ($start === 'UTC-') {
301 $offset = -$offset;
302 }
303 return $offset;
304 }
305 /**
306 * Converts a timestamp from UTC to a timezone.
307 *
308 * @param int $timestamp The UNIX timestamp to adjust.
309 * @param string $timezone The timezone to adjust to.
310 * @return int The adjusted time as seconds from EPOCH.
311 */
312 public static function adjustForTimezone($timestamp, $timezone)
313 {
314 if (empty($timezone)) {
315 return $timestamp;
316 }
317 // manually adjust for UTC timezones
318 $utcOffset = self::extractUtcOffset($timezone);
319 if ($utcOffset !== \false) {
320 return self::addHourTo($timestamp, $utcOffset);
321 }
322 date_default_timezone_set($timezone);
323 $datetime = date(self::DATE_TIME_FORMAT, $timestamp);
324 date_default_timezone_set('UTC');
325 return strtotime($datetime);
326 }
327 /**
328 * Returns the date in the "Y-m-d H:i:s" PHP format
329 *
330 * @param int $timestamp
331 * @return string
332 */
333 public static function getDatetimeFromTimestamp($timestamp)
334 {
335 return date("Y-m-d H:i:s", $timestamp);
336 }
337 /**
338 * Returns the Unix timestamp of the date in UTC.
339 *
340 * @return int
341 */
342 public function getTimestampUTC()
343 {
344 return $this->timestamp;
345 }
346 /**
347 * Returns the unix timestamp of the date in UTC, converted from the current
348 * timestamp timezone.
349 *
350 * @return int
351 */
352 public function getTimestamp()
353 {
354 if (empty($this->timezone)) {
355 $this->timezone = 'UTC';
356 }
357 $utcOffset = self::extractUtcOffset($this->timezone);
358 if ($utcOffset !== \false) {
359 return (int) ($this->timestamp - $utcOffset * 3600);
360 }
361 // The following code seems clunky - I thought the DateTime php class would allow to return timestamps
362 // after applying the timezone offset. Instead, the underlying timestamp is not changed.
363 // I decided to get the date without the timezone information, and create the timestamp from the truncated string.
364 // Unit tests pass (@see Date.test.php) but I'm pretty sure this is not the right way to do it
365 date_default_timezone_set($this->timezone);
366 $dtzone = timezone_open('UTC');
367 $time = date('r', $this->timestamp);
368 $dtime = date_create($time);
369 date_timezone_set($dtime, $dtzone);
370 $dateWithTimezone = date_format($dtime, 'r');
371 $dateWithoutTimezone = substr($dateWithTimezone, 0, -6);
372 $timestamp = strtotime($dateWithoutTimezone);
373 date_default_timezone_set('UTC');
374 return (int) $timestamp;
375 }
376 /**
377 * Returns `true` if the current date is older than the given `$date`.
378 *
379 * @param Date $date
380 * @return bool
381 */
382 public function isLater(\Piwik\Date $date)
383 {
384 return $this->getTimestamp() > $date->getTimestamp();
385 }
386 /**
387 * Returns `true` if the current date is earlier than the given `$date`.
388 *
389 * @param Date $date
390 * @return bool
391 */
392 public function isEarlier(\Piwik\Date $date)
393 {
394 return $this->getTimestamp() < $date->getTimestamp();
395 }
396 /**
397 * Returns `true` if the current year is a leap year, false otherwise.
398 *
399 * @return bool
400 */
401 public function isLeapYear()
402 {
403 $isLeap = (bool) date('L', $this->getTimestamp());
404 return $isLeap;
405 }
406 /**
407 * Converts this date to the requested string format. See {@link https://php.net/date}
408 * for the list of format strings.
409 *
410 * @param string $format
411 * @return string
412 */
413 public function toString($format = 'Y-m-d')
414 {
415 return date($format, $this->getTimestamp());
416 }
417 /**
418 * See {@link toString()}.
419 *
420 * @return string The current date in `'YYYY-MM-DD'` format.
421 */
422 public function __toString()
423 {
424 return $this->toString();
425 }
426 /**
427 * Performs three-way comparison of the week of the current date against the given `$date`'s week.
428 *
429 * @param \Piwik\Date $date
430 * @return int Returns `0` if the current week is equal to `$date`'s, `-1` if the current week is
431 * earlier or `1` if the current week is later.
432 */
433 public function compareWeek(\Piwik\Date $date)
434 {
435 $currentWeek = date('W', $this->getTimestamp());
436 $toCompareWeek = date('W', $date->getTimestamp());
437 if ($currentWeek == $toCompareWeek) {
438 return 0;
439 }
440 if ($currentWeek < $toCompareWeek) {
441 return -1;
442 }
443 return 1;
444 }
445 /**
446 * Performs three-way comparison of the month of the current date against the given `$date`'s month.
447 *
448 * @param \Piwik\Date $date Month to compare
449 * @return int Returns `0` if the current month is equal to `$date`'s, `-1` if the current month is
450 * earlier or `1` if the current month is later.
451 */
452 public function compareMonth(\Piwik\Date $date)
453 {
454 $currentMonth = date('n', $this->getTimestamp());
455 $toCompareMonth = date('n', $date->getTimestamp());
456 if ($currentMonth == $toCompareMonth) {
457 return 0;
458 }
459 if ($currentMonth < $toCompareMonth) {
460 return -1;
461 }
462 return 1;
463 }
464 /**
465 * Performs three-way comparison of the month of the current date against the given `$date`'s year.
466 *
467 * @param \Piwik\Date $date Year to compare
468 * @return int Returns `0` if the current year is equal to `$date`'s, `-1` if the current year is
469 * earlier or `1` if the current year is later.
470 */
471 public function compareYear(\Piwik\Date $date)
472 {
473 $currentYear = date('Y', $this->getTimestamp());
474 $toCompareYear = date('Y', $date->getTimestamp());
475 if ($currentYear == $toCompareYear) {
476 return 0;
477 }
478 if ($currentYear < $toCompareYear) {
479 return -1;
480 }
481 return 1;
482 }
483 /**
484 * Returns `true` if current date is today.
485 *
486 * @return bool
487 */
488 public function isToday()
489 {
490 return $this->toString('Y-m-d') === \Piwik\Date::factory('today', $this->timezone)->toString('Y-m-d');
491 }
492 /**
493 * Returns a date object set to now in UTC (same as {@link today()}, except that the time is also set).
494 *
495 * @return \Piwik\Date
496 */
497 public static function now()
498 {
499 return new \Piwik\Date(self::getNowTimestamp());
500 }
501 /**
502 * Returns a date object set to today at midnight in UTC.
503 *
504 * @return \Piwik\Date
505 */
506 public static function today()
507 {
508 return new \Piwik\Date(strtotime(date("Y-m-d 00:00:00", self::getNowTimestamp())));
509 }
510 /**
511 * Returns a date object set to tomorrow at midnight in UTC.
512 *
513 * @return \Piwik\Date
514 */
515 public static function tomorrow()
516 {
517 return new \Piwik\Date(strtotime('tomorrow', self::getNowTimestamp()));
518 }
519 /**
520 * Returns a date object set to yesterday at midnight in UTC.
521 *
522 * @return \Piwik\Date
523 */
524 public static function yesterday()
525 {
526 return new \Piwik\Date(strtotime("yesterday", self::getNowTimestamp()));
527 }
528 /**
529 * Returns a date object set to yesterday with the current time of day in UTC.
530 *
531 * @return \Piwik\Date
532 */
533 public static function yesterdaySameTime()
534 {
535 return new \Piwik\Date(strtotime("yesterday " . date('H:i:s', self::getNowTimestamp()), self::getNowTimestamp()));
536 }
537 /**
538 * Returns a date object set to the day a week ago at midnight in UTC.
539 *
540 * @return \Piwik\Date
541 */
542 public static function lastWeek()
543 {
544 return new \Piwik\Date(strtotime("-1week 00:00:00", self::getNowTimestamp()));
545 }
546 /**
547 * Returns a date object set to the day a month ago at midnight in UTC.
548 *
549 * @return \Piwik\Date
550 */
551 public static function lastMonth()
552 {
553 return new \Piwik\Date(strtotime("-1month 00:00:00", self::getNowTimestamp()));
554 }
555 /**
556 * Returns a date object set to the day a year ago at midnight in UTC.
557 *
558 * @return \Piwik\Date
559 */
560 public static function lastYear()
561 {
562 return new \Piwik\Date(strtotime("-1year 00:00:00", self::getNowTimestamp()));
563 }
564 /**
565 * Returns a new Date instance with `$this` date's day and the specified new
566 * time of day.
567 *
568 * @param string $time String in the `'HH:MM:SS'` format.
569 * @return \Piwik\Date The new date with the time of day changed.
570 */
571 public function setTime($time)
572 {
573 return new \Piwik\Date(strtotime(date("Y-m-d", $this->timestamp) . " {$time}"), $this->timezone);
574 }
575 /**
576 * Returns a new Date instance with `$this` date's time of day and the day specified
577 * by `$day`.
578 *
579 * @param int $day The day eg. `31`.
580 * @return \Piwik\Date
581 */
582 public function setDay($day)
583 {
584 $ts = $this->timestamp;
585 $result = mktime(date('H', $ts), date('i', $ts), date('s', $ts), date('n', $ts), $day, date('Y', $ts));
586 return new \Piwik\Date($result, $this->timezone);
587 }
588 /**
589 * Returns a new Date instance with `$this` date's time of day, month and day, but with
590 * a new year (specified by `$year`).
591 *
592 * @param int $year The year, eg. `2010`.
593 * @return \Piwik\Date
594 */
595 public function setYear($year)
596 {
597 $ts = $this->timestamp;
598 $result = mktime(date('H', $ts), date('i', $ts), date('s', $ts), date('n', $ts), date('j', $ts), $year);
599 return new \Piwik\Date($result, $this->timezone);
600 }
601 /**
602 * Subtracts `$n` number of days from `$this` date and returns a new Date object.
603 *
604 * @param int $n An integer > 0.
605 * @return \Piwik\Date
606 */
607 public function subDay($n)
608 {
609 if ($n === 0) {
610 return clone $this;
611 }
612 $ts = strtotime("-{$n} day", $this->timestamp);
613 return new \Piwik\Date($ts, $this->timezone);
614 }
615 /**
616 * Subtracts `$n` weeks from `$this` date and returns a new Date object.
617 *
618 * @param int $n An integer > 0.
619 * @return \Piwik\Date
620 */
621 public function subWeek($n)
622 {
623 return $this->subDay(7 * $n);
624 }
625 /**
626 * Subtracts `$n` months from `$this` date and returns the result as a new Date object.
627 *
628 * @param int $n An integer > 0.
629 * @return \Piwik\Date new date
630 */
631 public function subMonth($n)
632 {
633 if ($n === 0) {
634 return clone $this;
635 }
636 $ts = $this->timestamp;
637 $result = mktime(
638 date('H', $ts),
639 date('i', $ts),
640 date('s', $ts),
641 date('n', $ts) - $n,
642 1,
643 // we set the day to 1
644 date('Y', $ts)
645 );
646 return new \Piwik\Date($result, $this->timezone);
647 }
648 /**
649 * Subtracts `$n` years from `$this` date and returns the result as a new Date object.
650 *
651 * @param int $n An integer > 0.
652 * @return \Piwik\Date
653 */
654 public function subYear($n)
655 {
656 if ($n === 0) {
657 return clone $this;
658 }
659 $ts = $this->timestamp;
660 $result = mktime(
661 date('H', $ts),
662 date('i', $ts),
663 date('s', $ts),
664 1,
665 // we set the month to 1
666 1,
667 // we set the day to 1
668 date('Y', $ts) - $n
669 );
670 return new \Piwik\Date($result, $this->timezone);
671 }
672 /**
673 * Returns a localized date string using the given template.
674 * The template should contain tags that will be replaced with localized date strings.
675 *
676 * @param string $template eg. `"MMM y"`
677 * @param bool $ucfirst whether the first letter should be upper-cased
678 * @return string eg. `"Aug 2009"`
679 */
680 public function getLocalized($template, $ucfirst = \true)
681 {
682 $dateTimeFormatProvider = StaticContainer::get('Piwik\\Intl\\Data\\Provider\\DateTimeFormatProvider');
683 $template = $dateTimeFormatProvider->getFormatPattern($template);
684 $tokens = self::parseFormat($template);
685 $out = '';
686 foreach ($tokens as $token) {
687 if (is_array($token)) {
688 $out .= $this->formatToken(array_shift($token));
689 } else {
690 $out .= $token;
691 }
692 }
693 if ($ucfirst) {
694 $out = mb_strtoupper(mb_substr($out, 0, 1)) . mb_substr($out, 1);
695 }
696 return $out;
697 }
698 protected function formatToken($token)
699 {
700 $dayOfWeek = $this->toString('N');
701 $monthOfYear = $this->toString('n');
702 $translator = StaticContainer::get('Piwik\\Translation\\Translator');
703 switch ($token) {
704 // year
705 case "yyyy":
706 case "y":
707 return $this->toString('Y');
708 case "yy":
709 return $this->toString('y');
710 // month
711 case "MMMM":
712 return $translator->translate('Intl_Month_Long_' . $monthOfYear);
713 case "MMM":
714 return $translator->translate('Intl_Month_Short_' . $monthOfYear);
715 case "MM":
716 return $this->toString('n');
717 case "M":
718 return $this->toString('m');
719 case "LLLL":
720 return $translator->translate('Intl_Month_Long_StandAlone_' . $monthOfYear);
721 case "LLL":
722 return $translator->translate('Intl_Month_Short_StandAlone_' . $monthOfYear);
723 case "LL":
724 return $this->toString('n');
725 case "L":
726 return $this->toString('m');
727 // day
728 case "dd":
729 return $this->toString('d');
730 case "d":
731 return $this->toString('j');
732 case "EEEE":
733 return $translator->translate('Intl_Day_Long_' . $dayOfWeek);
734 case "EEE":
735 case "EE":
736 case "E":
737 return $translator->translate('Intl_Day_Short_' . $dayOfWeek);
738 case "cccc":
739 return $translator->translate('Intl_Day_Long_StandAlone_' . $dayOfWeek);
740 case "ccc":
741 case "cc":
742 case "c":
743 return $translator->translate('Intl_Day_Short_StandAlone_' . $dayOfWeek);
744 case "D":
745 return 1 + (int) $this->toString('z');
746 // 1 - 366
747 case "F":
748 return (int) (((int) $this->toString('j') + 6) / 7);
749 // week in month
750 case "w":
751 $weekDay = date('N', mktime(0, 0, 0, $this->toString('m'), 1, $this->toString('y')));
752 return floor(($weekDay + (int) $this->toString('m') - 2) / 7) + 1;
753 // week in year
754 case "W":
755 return $this->toString('N');
756 // hour
757 case "HH":
758 return $this->toString('H');
759 case "H":
760 return $this->toString('G');
761 case "hh":
762 return $this->toString('h');
763 case "h":
764 return $this->toString('g');
765 case "KK":
766 // 00 .. 11
767 return str_pad($this->toString('g') - 1, 2, '0');
768 case "K":
769 // 0 .. 11
770 return $this->toString('g') - 1;
771 case "kk":
772 // 01 .. 24
773 return str_pad($this->toString('G') + 1, 2, '0');
774 case "k":
775 // 1 .. 24
776 return $this->toString('G') + 1;
777 // minute
778 case "mm":
779 case "m":
780 return $this->toString('i');
781 // second
782 case "ss":
783 case "s":
784 return $this->toString('s');
785 // would normally also include AM, PM, Noon and Midnight
786 case "b":
787 // would normally be a textual presentation like "in the afternoon"
788 case "B":
789 // am / pm
790 case "a":
791 return $this->toString('a') == 'am' ? $translator->translate('Intl_Time_AM') : $translator->translate('Intl_Time_PM');
792 // currently not implemented:
793 case "G":
794 case "GG":
795 case "GGG":
796 case "GGGG":
797 case "GGGGG":
798 return '';
799 // era
800 case "z":
801 case "Z":
802 case "v":
803 return '';
804 }
805 return '';
806 }
807 protected static $tokens = array('G', 'y', 'M', 'L', 'd', 'h', 'H', 'k', 'K', 'm', 's', 'E', 'c', 'e', 'D', 'F', 'w', 'W', 'a', 'b', 'B', 'z', 'Z', 'v');
808 /**
809 * Parses the datetime format pattern and returns a tokenized result array
810 *
811 * Examples:
812 * Input Output
813 * 'dd.mm.yyyy' array(array('dd'), '.', array('mm'), '.', array('yyyy'))
814 * 'y?M?d?EEEE ah:mm:ss' array(array('y'), '?', array('M'), '?', array('d'), '?', array('EEEE'), ' ', array('a'), array('h'), ':', array('mm'), ':', array('ss'))
815 *
816 * @param string $pattern the pattern to be parsed
817 * @return array tokenized parsing result
818 */
819 protected static function parseFormat($pattern)
820 {
821 static $formats = array();
822 // cache
823 if (isset($formats[$pattern])) {
824 return $formats[$pattern];
825 }
826 $tokens = array();
827 $n = strlen($pattern);
828 $isLiteral = \false;
829 $literal = '';
830 for ($i = 0; $i < $n; ++$i) {
831 $c = $pattern[$i];
832 if ($c === "'") {
833 if ($i < $n - 1 && $pattern[$i + 1] === "'") {
834 $tokens[] = "'";
835 $i++;
836 } elseif ($isLiteral) {
837 $tokens[] = $literal;
838 $literal = '';
839 $isLiteral = \false;
840 } else {
841 $isLiteral = \true;
842 $literal = '';
843 }
844 } elseif ($isLiteral) {
845 $literal .= $c;
846 } else {
847 for ($j = $i + 1; $j < $n; ++$j) {
848 if ($pattern[$j] !== $c) {
849 break;
850 }
851 }
852 $p = str_repeat($c, $j - $i);
853 if (in_array($c, self::$tokens)) {
854 $tokens[] = array($p);
855 } else {
856 $tokens[] = $p;
857 }
858 $i = $j - 1;
859 }
860 }
861 if ($literal !== '') {
862 $tokens[] = $literal;
863 }
864 return $formats[$pattern] = $tokens;
865 }
866 /**
867 * Adds `$n` days to `$this` date and returns the result in a new Date.
868 * instance.
869 *
870 * @param int $n Number of days to add, must be > 0.
871 * @return \Piwik\Date
872 */
873 public function addDay($n)
874 {
875 $ts = strtotime("+{$n} day", $this->timestamp);
876 return new \Piwik\Date($ts, $this->timezone);
877 }
878 /**
879 * Adds `$n` Month to `$this` date and returns the result in a new Date.
880 * instance.
881 *
882 * @param int $n Number of days to add, must be > 0.
883 * @return \Piwik\Date
884 */
885 public function addMonth($n)
886 {
887 $ts = strtotime("+{$n} month", $this->timestamp);
888 return new \Piwik\Date($ts, $this->timezone);
889 }
890 /**
891 * Adds `$n` hours to `$this` date and returns the result in a new Date.
892 *
893 * @param int|float $n Number of hours to add. Can be less than 0, can be decimal (will get converted to minutes)
894 * @return \Piwik\Date
895 */
896 public function addHour($n)
897 {
898 $ts = self::addHourTo($this->timestamp, $n);
899 return new \Piwik\Date($ts, $this->timezone);
900 }
901 /**
902 * Adds N number of hours to a UNIX timestamp and returns the result. Using
903 * this static function instead of {@link addHour()} will be faster since a
904 * Date instance does not have to be created.
905 *
906 * @param int|float $timestamp The timestamp to add to.
907 * @param number $n Number of hours to add, must be > 0.
908 * @return int The result as a UNIX timestamp.
909 */
910 public static function addHourTo($timestamp, $n)
911 {
912 $isNegative = $n < 0;
913 $minutes = 0;
914 if ($n != round($n)) {
915 if ($n >= 1 || $n <= -1) {
916 $extraMinutes = floor(abs($n));
917 if ($isNegative) {
918 $extraMinutes = -$extraMinutes;
919 }
920 $minutes = abs($n - $extraMinutes) * 60;
921 if ($isNegative) {
922 $minutes *= -1;
923 }
924 } else {
925 $minutes = $n * 60;
926 }
927 $n = floor(abs($n));
928 if ($isNegative) {
929 $n *= -1;
930 }
931 }
932 return (int) ($timestamp + round($minutes * 60) + $n * 3600);
933 }
934 /**
935 * Subtracts `$n` hours from `$this` date and returns the result in a new Date.
936 *
937 * @param int $n Number of hours to subtract. Can be less than 0.
938 * @return \Piwik\Date
939 */
940 public function subHour($n)
941 {
942 return $this->addHour(-$n);
943 }
944 /**
945 * Subtracts `$n` seconds from `$this` date and returns the result in a new Date.
946 *
947 * @param int $n Number of seconds to subtract. Can be less than 0.
948 * @return \Piwik\Date
949 */
950 public function subSeconds($n)
951 {
952 return new \Piwik\Date($this->timestamp - $n, $this->timezone);
953 }
954 /**
955 * Adds a period to `$this` date and returns the result in a new Date instance.
956 *
957 * @param int $n The number of periods to add. Can be negative.
958 * @param string $period The type of period to add (YEAR, MONTH, WEEK, DAY, ...)
959 * @return \Piwik\Date
960 */
961 public function addPeriod($n, $period)
962 {
963 if (strtolower($period) == 'month') {
964 // TODO: comments
965 $dateInfo = getdate($this->timestamp);
966 $ts = mktime($dateInfo['hours'], $dateInfo['minutes'], $dateInfo['seconds'], $dateInfo['mon'] + (int) $n, 1, $dateInfo['year']);
967 $daysToAdd = min($dateInfo['mday'], self::getMaxDaysInMonth($ts)) - 1;
968 $ts += self::NUM_SECONDS_IN_DAY * $daysToAdd;
969 } else {
970 $time = $n < 0 ? "{$n} {$period}" : "+{$n} {$period}";
971 $ts = strtotime($time, $this->timestamp);
972 }
973 return new \Piwik\Date($ts, $this->timezone);
974 }
975 private static function getMaxDaysInMonth($timestamp)
976 {
977 $month = (int) date('m', $timestamp);
978 if (date('L', $timestamp) == 1 && $month == 2) {
979 return 29;
980 } else {
981 return self::$maxDaysInMonth[$month];
982 }
983 }
984 /**
985 * Subtracts a period from `$this` date and returns the result in a new Date instance.
986 *
987 * @param int $n The number of periods to add. Can be negative.
988 * @param string $period The type of period to add (YEAR, MONTH, WEEK, DAY, ...)
989 * @return \Piwik\Date
990 */
991 public function subPeriod($n, $period)
992 {
993 return $this->addPeriod(-$n, $period);
994 }
995 /**
996 * Returns the number of days represented by a number of seconds.
997 *
998 * @param int $secs
999 * @return float
1000 */
1001 public static function secondsToDays($secs)
1002 {
1003 return $secs / self::NUM_SECONDS_IN_DAY;
1004 }
1005 private static function getInvalidDateFormatException($dateString)
1006 {
1007 $message = \Piwik\Piwik::translate('General_ExceptionInvalidDateFormat', array("YYYY-MM-DD, or 'today' or 'yesterday'", "strtotime", "https://php.net/strtotime"));
1008 return new Exception($message . ": " . var_export($dateString, \true));
1009 }
1010 /**
1011 * For tests.
1012 * @return int|null
1013 * @ignore
1014 */
1015 public static function getNowTimestamp()
1016 {
1017 return isset(self::$now) ? self::$now : time();
1018 }
1019 }
1020