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