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