PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
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 3 months ago Access 3 months ago Application 3 months ago Archive 3 months ago ArchiveProcessor 3 months ago Archiver 2 years ago AssetManager 3 months ago Auth 6 months ago Category 6 months ago Changes 3 months ago CliMulti 1 year ago Columns 3 months ago Concurrency 3 months ago Config 3 months ago Container 3 months ago CronArchive 3 months ago DataAccess 3 months ago DataFiles 2 years ago DataTable 3 months ago Db 3 months ago DeviceDetector 1 year ago Email 2 years ago Exception 4 months ago Http 4 months ago Intl 3 months ago Log 2 years ago Mail 1 year ago Measurable 6 months ago Menu 3 months ago Metrics 3 months ago Notification 6 months ago Period 3 months ago Plugin 3 months ago Policy 3 months ago ProfessionalServices 1 year ago Report 1 year ago ReportRenderer 3 months ago Request 3 months ago Scheduler 3 months ago Segment 3 months ago Session 3 months ago Settings 3 months ago Tracker 3 months ago Translation 3 months ago Twig 1 year ago UpdateCheck 3 months ago Updater 4 months ago Updates 3 months ago Validators 1 year ago View 6 months ago ViewDataTable 3 months ago Visualization 1 year ago Widget 3 months ago .htaccess 2 years ago Access.php 3 months ago Archive.php 3 months ago ArchiveProcessor.php 4 months ago AssetManager.php 3 months ago Auth.php 6 months ago AuthResult.php 6 months ago BaseFactory.php 2 years ago Cache.php 2 years ago CacheId.php 4 months ago CliMulti.php 3 months ago Common.php 3 months ago Config.php 3 months ago Console.php 3 months ago Context.php 2 years ago Cookie.php 1 year ago CronArchive.php 3 months ago DI.php 3 months ago DataArray.php 5 months ago DataTable.php 3 months ago Date.php 3 months ago Db.php 3 months ago DbHelper.php 3 months ago Development.php 1 year ago ErrorHandler.php 6 months ago EventDispatcher.php 1 year ago ExceptionHandler.php 4 months ago FileIntegrity.php 3 months ago Filechecks.php 1 year ago Filesystem.php 3 months ago FrontController.php 4 months ago Http.php 4 months ago IP.php 1 year ago Log.php 3 months ago LogDeleter.php 1 year ago Mail.php 1 year ago Metrics.php 3 months ago NoAccessException.php 2 years ago Nonce.php 6 months ago Notification.php 6 months ago NumberFormatter.php 5 months ago Option.php 5 months ago Period.php 3 months ago Piwik.php 3 months ago Plugin.php 3 months ago Process.php 1 year ago Profiler.php 6 months ago ProxyHeaders.php 4 months ago ProxyHttp.php 5 months ago QuickForm2.php 3 months ago RankingQuery.php 5 months ago ReportRenderer.php 3 months ago Request.php 3 months ago Segment.php 3 months ago Sequence.php 6 months ago Session.php 3 months ago SettingsPiwik.php 3 months ago SettingsServer.php 1 year ago Singleton.php 2 years ago Site.php 4 months ago SiteContentDetector.php 3 months ago SupportedBrowser.php 2 years ago TCPDF.php 1 year ago Theme.php 1 year ago Timer.php 2 years ago Tracker.php 3 months ago Twig.php 3 months ago Unzip.php 1 year ago UpdateCheck.php 3 months ago Updater.php 3 months ago UpdaterErrorException.php 2 years ago Updates.php 3 months ago Url.php 3 months ago UrlHelper.php 3 months ago Version.php 3 months ago View.php 3 months ago bootstrap.php 1 year ago dispatch.php 2 years ago testMinimumPhpVersion.php 6 months ago
Date.php
1026 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 array<int, 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|Date $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 * @return Date
98 * @throws Exception If `$dateString` is in an invalid format or if the time is before
99 * Tue, 06 Aug 1991.
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 string $dateString
144 * @param string $timezone
145 * @return Date
146 * @ignore
147 */
148 public static function factoryInTimezone($dateString, $timezone)
149 {
150 if ($dateString === 'now') {
151 return self::nowInTimezone((string) $timezone);
152 } elseif ($dateString === 'today') {
153 return self::todayInTimezone((string) $timezone);
154 } elseif ($dateString === 'yesterday') {
155 return self::yesterdayInTimezone((string) $timezone);
156 } elseif ($dateString === 'yesterdaySameTime') {
157 return self::yesterdaySameTimeInTimezone((string) $timezone);
158 } elseif (preg_match('/last[ -]?week/i', urldecode($dateString))) {
159 return self::lastWeekInTimezone((string) $timezone);
160 } elseif (preg_match('/last[ -]?month/i', urldecode($dateString))) {
161 return self::lastMonthInTimezone((string) $timezone);
162 } elseif (preg_match('/last[ -]?year/i', urldecode($dateString))) {
163 return self::lastYearInTimezone((string) $timezone);
164 } else {
165 throw new \Exception("Date::factoryInTimezone() should not be used with {$dateString}.");
166 }
167 }
168 private static function nowInTimezone(string $timezone) : \Piwik\Date
169 {
170 $now = self::getNowTimestamp();
171 $now = self::adjustForTimezone($now, $timezone);
172 return new \Piwik\Date($now);
173 }
174 private static function todayInTimezone(string $timezone) : \Piwik\Date
175 {
176 return self::nowInTimezone($timezone)->getStartOfDay();
177 }
178 private static function yesterdayInTimezone(string $timezone) : \Piwik\Date
179 {
180 return self::todayInTimezone($timezone)->subDay(1);
181 }
182 private static function yesterdaySameTimeInTimezone(string $timezone) : \Piwik\Date
183 {
184 return self::nowInTimezone($timezone)->subDay(1);
185 }
186 private static function lastWeekInTimezone(string $timezone) : \Piwik\Date
187 {
188 return new \Piwik\Date(strtotime('-1week', self::todayInTimezone($timezone)->getTimestamp()));
189 }
190 private static function lastMonthInTimezone(string $timezone) : \Piwik\Date
191 {
192 return new \Piwik\Date(strtotime('-1month', self::todayInTimezone($timezone)->getTimestamp()));
193 }
194 private static function lastYearInTimezone(string $timezone) : \Piwik\Date
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|float|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 * @return bool
380 */
381 public function isLater(\Piwik\Date $date)
382 {
383 return $this->getTimestamp() > $date->getTimestamp();
384 }
385 /**
386 * Returns `true` if the current date is earlier than the given `$date`.
387 *
388 * @return bool
389 */
390 public function isEarlier(\Piwik\Date $date)
391 {
392 return $this->getTimestamp() < $date->getTimestamp();
393 }
394 /**
395 * Returns `true` if the current year is a leap year, false otherwise.
396 *
397 * @return bool
398 */
399 public function isLeapYear()
400 {
401 $isLeap = (bool) date('L', $this->getTimestamp());
402 return $isLeap;
403 }
404 /**
405 * Converts this date to the requested string format. See {@link https://php.net/date}
406 * for the list of format strings.
407 *
408 * @param string $format
409 * @return string
410 */
411 public function toString($format = 'Y-m-d')
412 {
413 return date($format, $this->getTimestamp());
414 }
415 /**
416 * See {@link toString()}.
417 *
418 * @return string The current date in `'YYYY-MM-DD'` format.
419 */
420 public function __toString()
421 {
422 return $this->toString();
423 }
424 /**
425 * Performs three-way comparison of the week of the current date against the given `$date`'s week.
426 *
427 * @return int Returns `0` if the current week is equal to `$date`'s, `-1` if the current week is
428 * earlier or `1` if the current week is later.
429 */
430 public function compareWeek(\Piwik\Date $date)
431 {
432 $currentWeek = date('W', $this->getTimestamp());
433 $toCompareWeek = date('W', $date->getTimestamp());
434 if ($currentWeek == $toCompareWeek) {
435 return 0;
436 }
437 if ($currentWeek < $toCompareWeek) {
438 return -1;
439 }
440 return 1;
441 }
442 /**
443 * Performs three-way comparison of the month of the current date against the given `$date`'s month.
444 *
445 * @param \Piwik\Date $date Month to compare
446 * @return int Returns `0` if the current month is equal to `$date`'s, `-1` if the current month is
447 * earlier or `1` if the current month is later.
448 */
449 public function compareMonth(\Piwik\Date $date)
450 {
451 $currentMonth = date('n', $this->getTimestamp());
452 $toCompareMonth = date('n', $date->getTimestamp());
453 if ($currentMonth == $toCompareMonth) {
454 return 0;
455 }
456 if ($currentMonth < $toCompareMonth) {
457 return -1;
458 }
459 return 1;
460 }
461 /**
462 * Performs three-way comparison of the month of the current date against the given `$date`'s year.
463 *
464 * @param \Piwik\Date $date Year to compare
465 * @return int Returns `0` if the current year is equal to `$date`'s, `-1` if the current year is
466 * earlier or `1` if the current year is later.
467 */
468 public function compareYear(\Piwik\Date $date)
469 {
470 $currentYear = date('Y', $this->getTimestamp());
471 $toCompareYear = date('Y', $date->getTimestamp());
472 if ($currentYear == $toCompareYear) {
473 return 0;
474 }
475 if ($currentYear < $toCompareYear) {
476 return -1;
477 }
478 return 1;
479 }
480 /**
481 * Returns `true` if current date is today.
482 *
483 * @return bool
484 */
485 public function isToday()
486 {
487 return $this->toString('Y-m-d') === \Piwik\Date::factory('today', $this->timezone)->toString('Y-m-d');
488 }
489 /**
490 * Returns a date object set to now in UTC (same as {@link today()}, except that the time is also set).
491 *
492 * @return \Piwik\Date
493 */
494 public static function now()
495 {
496 return new \Piwik\Date(self::getNowTimestamp());
497 }
498 /**
499 * Returns a date object set to today at midnight in UTC.
500 *
501 * @return \Piwik\Date
502 */
503 public static function today()
504 {
505 return new \Piwik\Date(strtotime(date("Y-m-d 00:00:00", self::getNowTimestamp())));
506 }
507 /**
508 * Returns a date object set to tomorrow at midnight in UTC.
509 *
510 * @return \Piwik\Date
511 */
512 public static function tomorrow()
513 {
514 return new \Piwik\Date(strtotime('tomorrow', self::getNowTimestamp()));
515 }
516 /**
517 * Returns a date object set to yesterday at midnight in UTC.
518 *
519 * @return \Piwik\Date
520 */
521 public static function yesterday()
522 {
523 return new \Piwik\Date(strtotime("yesterday", self::getNowTimestamp()));
524 }
525 /**
526 * Returns a date object set to yesterday with the current time of day in UTC.
527 *
528 * @return \Piwik\Date
529 */
530 public static function yesterdaySameTime()
531 {
532 return new \Piwik\Date(strtotime("yesterday " . date('H:i:s', self::getNowTimestamp()), self::getNowTimestamp()));
533 }
534 /**
535 * Returns a date object set to the day a week ago at midnight in UTC.
536 *
537 * @return \Piwik\Date
538 */
539 public static function lastWeek()
540 {
541 return new \Piwik\Date(strtotime("-1week 00:00:00", self::getNowTimestamp()));
542 }
543 /**
544 * Returns a date object set to the day a month ago at midnight in UTC.
545 *
546 * @return \Piwik\Date
547 */
548 public static function lastMonth()
549 {
550 return new \Piwik\Date(strtotime("-1month 00:00:00", self::getNowTimestamp()));
551 }
552 /**
553 * Returns a date object set to the day a year ago at midnight in UTC.
554 *
555 * @return \Piwik\Date
556 */
557 public static function lastYear()
558 {
559 return new \Piwik\Date(strtotime("-1year 00:00:00", self::getNowTimestamp()));
560 }
561 /**
562 * Returns a new Date instance with `$this` date's day and the specified new
563 * time of day.
564 *
565 * @param string $time String in the `'HH:MM:SS'` format.
566 * @return \Piwik\Date The new date with the time of day changed.
567 */
568 public function setTime($time)
569 {
570 return new \Piwik\Date(strtotime(date("Y-m-d", $this->timestamp) . " {$time}"), $this->timezone);
571 }
572 /**
573 * Returns a new Date instance with `$this` date's time of day and the day specified
574 * by `$day`.
575 *
576 * @param int $day The day eg. `31`.
577 * @return \Piwik\Date
578 */
579 public function setDay($day)
580 {
581 $ts = $this->timestamp;
582 $result = mktime((int) date('H', $ts), (int) date('i', $ts), (int) date('s', $ts), (int) date('n', $ts), $day, (int) date('Y', $ts));
583 return new \Piwik\Date($result, $this->timezone);
584 }
585 /**
586 * Returns a new Date instance with `$this` date's time of day, month and day, but with
587 * a new year (specified by `$year`).
588 *
589 * @param int $year The year, eg. `2010`.
590 * @return \Piwik\Date
591 */
592 public function setYear($year)
593 {
594 $ts = $this->timestamp;
595 $result = mktime((int) date('H', $ts), (int) date('i', $ts), (int) date('s', $ts), (int) date('n', $ts), (int) date('j', $ts), $year);
596 return new \Piwik\Date($result, $this->timezone);
597 }
598 /**
599 * Subtracts `$n` number of days from `$this` date and returns a new Date object.
600 *
601 * @param int $n An integer > 0.
602 * @return \Piwik\Date
603 */
604 public function subDay($n)
605 {
606 if ($n === 0) {
607 return clone $this;
608 }
609 $ts = strtotime("-{$n} day", $this->timestamp);
610 return new \Piwik\Date($ts, $this->timezone);
611 }
612 /**
613 * Subtracts `$n` weeks from `$this` date and returns a new Date object.
614 *
615 * @param int $n An integer > 0.
616 * @return \Piwik\Date
617 */
618 public function subWeek($n)
619 {
620 return $this->subDay(7 * $n);
621 }
622 /**
623 * Subtracts `$n` months from `$this` date and returns the result as a new Date object.
624 *
625 * @param int $n An integer > 0.
626 * @return \Piwik\Date new date
627 */
628 public function subMonth($n)
629 {
630 if ($n === 0) {
631 return clone $this;
632 }
633 $ts = $this->timestamp;
634 $result = mktime(
635 (int) date('H', $ts),
636 (int) date('i', $ts),
637 (int) date('s', $ts),
638 (int) date('n', $ts) - $n,
639 1,
640 // we set the day to 1
641 (int) date('Y', $ts)
642 );
643 return new \Piwik\Date($result, $this->timezone);
644 }
645 /**
646 * Subtracts `$n` years from `$this` date and returns the result as a new Date object.
647 *
648 * @param int $n An integer > 0.
649 * @return \Piwik\Date
650 */
651 public function subYear($n)
652 {
653 if ($n === 0) {
654 return clone $this;
655 }
656 $ts = $this->timestamp;
657 $result = mktime(
658 (int) date('H', $ts),
659 (int) date('i', $ts),
660 (int) date('s', $ts),
661 1,
662 // we set the month to 1
663 1,
664 // we set the day to 1
665 (int) date('Y', $ts) - $n
666 );
667 return new \Piwik\Date($result, $this->timezone);
668 }
669 /**
670 * Returns a localized date string using the given template.
671 * The template should contain tags that will be replaced with localized date strings.
672 *
673 * @param string|int $template eg. `"MMM y"` or any format constant defined in {@link DateTimeFormatProvider}
674 * @param bool $ucfirst whether the first letter should be upper-cased
675 * @return string eg. `"Aug 2009"`
676 */
677 public function getLocalized($template, $ucfirst = \true)
678 {
679 $dateTimeFormatProvider = StaticContainer::get(DateTimeFormatProvider::class);
680 $template = $dateTimeFormatProvider->getFormatPattern($template);
681 $tokens = self::parseFormat($template);
682 $out = '';
683 foreach ($tokens as $token) {
684 if (is_array($token)) {
685 $out .= $this->formatToken(array_shift($token));
686 } else {
687 $out .= $token;
688 }
689 }
690 if ($ucfirst) {
691 $out = mb_strtoupper(mb_substr($out, 0, 1)) . mb_substr($out, 1);
692 }
693 return $out;
694 }
695 /**
696 * @param string $token
697 * @return float|int|string
698 */
699 protected function formatToken($token)
700 {
701 $dayOfWeek = $this->toString('N');
702 $monthOfYear = $this->toString('n');
703 $translator = StaticContainer::get('Piwik\\Translation\\Translator');
704 switch ($token) {
705 // year
706 case "yyyy":
707 case "y":
708 return $this->toString('Y');
709 case "yy":
710 return $this->toString('y');
711 // month
712 case "MMMM":
713 return $translator->translate('Intl_Month_Long_' . $monthOfYear);
714 case "MMM":
715 return $translator->translate('Intl_Month_Short_' . $monthOfYear);
716 case "MM":
717 return $this->toString('n');
718 case "M":
719 return $this->toString('m');
720 case "LLLL":
721 return $translator->translate('Intl_Month_Long_StandAlone_' . $monthOfYear);
722 case "LLL":
723 return $translator->translate('Intl_Month_Short_StandAlone_' . $monthOfYear);
724 case "LL":
725 return $this->toString('n');
726 case "L":
727 return $this->toString('m');
728 // day
729 case "dd":
730 return $this->toString('d');
731 case "d":
732 return $this->toString('j');
733 case "EEEE":
734 return $translator->translate('Intl_Day_Long_' . $dayOfWeek);
735 case "EEE":
736 case "EE":
737 case "E":
738 return $translator->translate('Intl_Day_Short_' . $dayOfWeek);
739 case "cccc":
740 return $translator->translate('Intl_Day_Long_StandAlone_' . $dayOfWeek);
741 case "ccc":
742 case "cc":
743 case "c":
744 return $translator->translate('Intl_Day_Short_StandAlone_' . $dayOfWeek);
745 case "D":
746 return 1 + (int) $this->toString('z');
747 // 1 - 366
748 case "F":
749 return (int) (((int) $this->toString('j') + 6) / 7);
750 // week in month
751 case "w":
752 $weekDay = date('N', mktime(0, 0, 0, (int) $this->toString('m'), 1, (int) $this->toString('y')));
753 return floor(($weekDay + (int) $this->toString('m') - 2) / 7) + 1;
754 // week in year
755 case "W":
756 return $this->toString('N');
757 // hour
758 case "HH":
759 return $this->toString('H');
760 case "H":
761 return $this->toString('G');
762 case "hh":
763 return $this->toString('h');
764 case "h":
765 return $this->toString('g');
766 case "KK":
767 // 00 .. 11
768 return str_pad(strval((int) $this->toString('g') - 1), 2, '0');
769 case "K":
770 // 0 .. 11
771 return (int) $this->toString('g') - 1;
772 case "kk":
773 // 01 .. 24
774 return str_pad(strval((int) $this->toString('G') + 1), 2, '0');
775 case "k":
776 // 1 .. 24
777 return (int) $this->toString('G') + 1;
778 // minute
779 case "mm":
780 case "m":
781 return $this->toString('i');
782 // second
783 case "ss":
784 case "s":
785 return $this->toString('s');
786 // would normally also include AM, PM, Noon and Midnight
787 case "b":
788 // would normally be a textual presentation like "in the afternoon"
789 case "B":
790 // am / pm
791 case "a":
792 return $this->toString('a') == 'am' ? $translator->translate('Intl_Time_AM') : $translator->translate('Intl_Time_PM');
793 // currently not implemented:
794 case "G":
795 case "GG":
796 case "GGG":
797 case "GGGG":
798 case "GGGGG":
799 return '';
800 // era
801 case "z":
802 case "Z":
803 case "v":
804 return '';
805 }
806 return '';
807 }
808 /** @var string[] */
809 protected static $tokens = ['G', 'y', 'M', 'L', 'd', 'h', 'H', 'k', 'K', 'm', 's', 'E', 'c', 'e', 'D', 'F', 'w', 'W', 'a', 'b', 'B', 'z', 'Z', 'v'];
810 /**
811 * Parses the datetime format pattern and returns a tokenized result array
812 *
813 * Examples:
814 * Input Output
815 * 'dd.mm.yyyy' array(array('dd'), '.', array('mm'), '.', array('yyyy'))
816 * 'y?M?d?EEEE ah:mm:ss' array(array('y'), '?', array('M'), '?', array('d'), '?', array('EEEE'), ' ', array('a'), array('h'), ':', array('mm'), ':', array('ss'))
817 *
818 * @param string $pattern the pattern to be parsed
819 * @return array<string|string[]> tokenized parsing result
820 */
821 protected static function parseFormat($pattern)
822 {
823 static $formats = [];
824 // cache
825 if (isset($formats[$pattern])) {
826 return $formats[$pattern];
827 }
828 $tokens = [];
829 $n = strlen($pattern);
830 $isLiteral = \false;
831 $literal = '';
832 for ($i = 0; $i < $n; ++$i) {
833 $c = $pattern[$i];
834 if ($c === "'") {
835 if ($i < $n - 1 && $pattern[$i + 1] === "'") {
836 $tokens[] = "'";
837 $i++;
838 } elseif ($isLiteral) {
839 $tokens[] = $literal;
840 $literal = '';
841 $isLiteral = \false;
842 } else {
843 $isLiteral = \true;
844 $literal = '';
845 }
846 } elseif ($isLiteral) {
847 $literal .= $c;
848 } else {
849 for ($j = $i + 1; $j < $n; ++$j) {
850 if ($pattern[$j] !== $c) {
851 break;
852 }
853 }
854 $p = str_repeat($c, $j - $i);
855 if (in_array($c, self::$tokens)) {
856 $tokens[] = [$p];
857 } else {
858 $tokens[] = $p;
859 }
860 $i = $j - 1;
861 }
862 }
863 if ($literal !== '') {
864 $tokens[] = $literal;
865 }
866 return $formats[$pattern] = $tokens;
867 }
868 /**
869 * Adds `$n` days to `$this` date and returns the result in a new Date.
870 * instance.
871 *
872 * @param int $n Number of days to add, must be > 0.
873 * @return \Piwik\Date
874 */
875 public function addDay($n)
876 {
877 $ts = strtotime("+{$n} day", $this->timestamp);
878 return new \Piwik\Date($ts, $this->timezone);
879 }
880 /**
881 * Adds `$n` Month to `$this` date and returns the result in a new Date.
882 * instance.
883 *
884 * @param int $n Number of days to add, must be > 0.
885 * @return \Piwik\Date
886 */
887 public function addMonth($n)
888 {
889 $ts = strtotime("+{$n} month", $this->timestamp);
890 return new \Piwik\Date($ts, $this->timezone);
891 }
892 /**
893 * Adds `$n` hours to `$this` date and returns the result in a new Date.
894 *
895 * @param int|float $n Number of hours to add. Can be less than 0, can be decimal (will get converted to minutes)
896 * @return \Piwik\Date
897 */
898 public function addHour($n)
899 {
900 $ts = self::addHourTo($this->timestamp, $n);
901 return new \Piwik\Date($ts, $this->timezone);
902 }
903 /**
904 * Adds N number of hours to a UNIX timestamp and returns the result. Using
905 * this static function instead of {@link addHour()} will be faster since a
906 * Date instance does not have to be created.
907 *
908 * @param int|float $timestamp The timestamp to add to.
909 * @param number $n Number of hours to add, must be > 0.
910 * @return int The result as a UNIX timestamp.
911 */
912 public static function addHourTo($timestamp, $n)
913 {
914 $isNegative = $n < 0;
915 $minutes = 0;
916 if ($n != round($n)) {
917 if ($n >= 1 || $n <= -1) {
918 $extraMinutes = floor(abs($n));
919 if ($isNegative) {
920 $extraMinutes = -$extraMinutes;
921 }
922 $minutes = abs($n - $extraMinutes) * 60;
923 if ($isNegative) {
924 $minutes *= -1;
925 }
926 } else {
927 $minutes = $n * 60;
928 }
929 $n = floor(abs($n));
930 if ($isNegative) {
931 $n *= -1;
932 }
933 }
934 return (int) ($timestamp + round($minutes * 60) + $n * 3600);
935 }
936 /**
937 * Subtracts `$n` hours from `$this` date and returns the result in a new Date.
938 *
939 * @param int $n Number of hours to subtract. Can be less than 0.
940 * @return \Piwik\Date
941 */
942 public function subHour($n)
943 {
944 return $this->addHour(-$n);
945 }
946 /**
947 * Subtracts `$n` seconds from `$this` date and returns the result in a new Date.
948 *
949 * @param int $n Number of seconds to subtract. Can be less than 0.
950 * @return \Piwik\Date
951 */
952 public function subSeconds($n)
953 {
954 return new \Piwik\Date($this->timestamp - $n, $this->timezone);
955 }
956 /**
957 * Adds a period to `$this` date and returns the result in a new Date instance.
958 *
959 * @param int $n The number of periods to add. Can be negative.
960 * @param string $period The type of period to add (YEAR, MONTH, WEEK, DAY, ...)
961 * @return \Piwik\Date
962 */
963 public function addPeriod($n, $period)
964 {
965 if (strtolower($period) == 'month') {
966 // TODO: comments
967 $dateInfo = getdate($this->timestamp);
968 $ts = mktime($dateInfo['hours'], $dateInfo['minutes'], $dateInfo['seconds'], $dateInfo['mon'] + (int) $n, 1, $dateInfo['year']);
969 $daysToAdd = min($dateInfo['mday'], self::getMaxDaysInMonth($ts)) - 1;
970 $ts += self::NUM_SECONDS_IN_DAY * $daysToAdd;
971 } else {
972 $time = $n < 0 ? "{$n} {$period}" : "+{$n} {$period}";
973 $ts = strtotime($time, $this->timestamp);
974 }
975 return new \Piwik\Date($ts, $this->timezone);
976 }
977 private static function getMaxDaysInMonth(int $timestamp) : int
978 {
979 $month = (int) date('m', $timestamp);
980 if (date('L', $timestamp) == 1 && $month == 2) {
981 return 29;
982 } else {
983 return self::$maxDaysInMonth[$month];
984 }
985 }
986 /**
987 * Subtracts a period from `$this` date and returns the result in a new Date instance.
988 *
989 * @param int $n The number of periods to add. Can be negative.
990 * @param string $period The type of period to add (YEAR, MONTH, WEEK, DAY, ...)
991 * @return \Piwik\Date
992 */
993 public function subPeriod($n, $period)
994 {
995 return $this->addPeriod(-$n, $period);
996 }
997 /**
998 * Returns the number of days represented by a number of seconds.
999 *
1000 * @param int $secs
1001 * @return float
1002 */
1003 public static function secondsToDays($secs)
1004 {
1005 return $secs / self::NUM_SECONDS_IN_DAY;
1006 }
1007 /**
1008 * @param mixed $dateString
1009 * @return Exception
1010 */
1011 private static function getInvalidDateFormatException($dateString)
1012 {
1013 $message = \Piwik\Piwik::translate('General_ExceptionInvalidDateFormat', array("YYYY-MM-DD, or 'today' or 'yesterday'", "strtotime", "https://php.net/strtotime"));
1014 return new Exception($message . ": " . var_export($dateString, \true));
1015 }
1016 /**
1017 * For tests.
1018 * @return int|null
1019 * @ignore
1020 */
1021 public static function getNowTimestamp()
1022 {
1023 return isset(self::$now) ? self::$now : time();
1024 }
1025 }
1026