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