timezone = $timezone; $this->timestamp = $timestamp ; } /** * Returns a Piwik_Date objects. * * @param string|self $dateString 'today' 'yesterday' or any YYYY-MM-DD or timestamp * @param string $timezone if specified, the dateString will be relative to this $timezone. * For example, today in UTC+12 will be a timestamp in the future for UTC. * This is different from using ->setTimezone() * @throws Exception * @return Piwik_Date */ static public function factory($dateString, $timezone = null) { $invalidDateException = new Exception(Piwik_TranslateException('General_ExceptionInvalidDateFormat', array("YYYY-MM-DD, or 'today' or 'yesterday'", "strtotime", "http://php.net/strtotime")).": $dateString"); if($dateString instanceof self) { $dateString = $dateString->toString(); } if($dateString == 'now') { $date = self::now(); } elseif($dateString == 'today') { $date = self::today(); } elseif($dateString == 'yesterday') { $date = self::yesterday(); } elseif($dateString == 'yesterdaySameTime') { $date = self::yesterdaySameTime(); } elseif (!is_int($dateString) && ( // strtotime returns the timestamp for April 1st for a date like 2011-04-01,today // but we don't want this, as this is a date range and supposed to throw the exception strpos($dateString, ',') !== false || ($dateString = strtotime($dateString)) === false )) { throw $invalidDateException; } else { $date = new Piwik_Date($dateString); } $timestamp = $date->getTimestamp(); // can't be doing web analytics before the 1st website // Tue, 06 Aug 1991 00:00:00 GMT if($timestamp < 681436800 ) { throw $invalidDateException; } if(empty($timezone)) { return $date; } $timestamp = self::adjustForTimezone($timestamp, $timezone); return Piwik_Date::factory($timestamp); } /** * Returns the datetime of the current timestamp * * @return string */ function getDatetime() { return $this->toString(self::DATE_TIME_FORMAT); } /** * Returns the datetime start in UTC * * @return string */ function getDateStartUTC() { $dateStartUTC = gmdate('Y-m-d', $this->timestamp); $date = Piwik_Date::factory($dateStartUTC)->setTimezone($this->timezone); return $date->toString(self::DATE_TIME_FORMAT); } /** * Returns the datetime end in UTC * * @return string */ function getDateEndUTC() { $dateEndUTC = gmdate('Y-m-d 23:59:59', $this->timestamp); $date = Piwik_Date::factory($dateEndUTC)->setTimezone($this->timezone); return $date->toString(self::DATE_TIME_FORMAT); } /** * Returns a new date object, copy of $this, with the timezone set * This timezone is used to offset the UTC timestamp returned by @see getTimestamp() * Doesn't modify $this * * @param string $timezone 'UTC', 'Europe/London', ... * @return Piwik_Date */ public function setTimezone($timezone) { return new Piwik_Date($this->timestamp, $timezone); } /** * Helper function that returns the offset in the timezone string 'UTC+14' * Returns false if the timezone is not UTC+X or UTC-X * * @param string $timezone * @return int|bool utc offset or false */ static protected function extractUtcOffset($timezone) { if($timezone == 'UTC') { return 0; } $start = substr($timezone, 0, 4); if($start != 'UTC-' && $start != 'UTC+') { return false; } $offset = (float)substr($timezone, 4); if($start == 'UTC-') { $offset = -$offset; } return $offset; } /** * Adjusts a UNIX timestamp in UTC to a specific timezone. * * @param int $timestamp The UNIX timestamp to adjust. * @param string $timezone The timezone to adjust to. * @return int The adjusted time as seconds from EPOCH. */ static public function adjustForTimezone($timestamp, $timezone) { // manually adjust for UTC timezones $utcOffset = self::extractUtcOffset($timezone); if($utcOffset !== false) { return self::addHourTo($timestamp, $utcOffset); } date_default_timezone_set($timezone); $datetime = date(self::DATE_TIME_FORMAT, $timestamp); date_default_timezone_set('UTC'); return strtotime($datetime); } /** * Returns the Unix timestamp of the date in UTC * * @return int */ public function getTimestampUTC() { return $this->timestamp; } /** * Returns the unix timestamp of the date in UTC, * converted from the date timezone * * @return int */ public function getTimestamp() { if(empty($this->timezone)) { $this->timezone = 'UTC'; } $utcOffset = self::extractUtcOffset($this->timezone); if($utcOffset !== false) { return (int)($this->timestamp - $utcOffset * 3600); } // The following code seems clunky - I thought the DateTime php class would allow to return timestamps // after applying the timezone offset. Instead, the underlying timestamp is not changed. // I decided to get the date without the timezone information, and create the timestamp from the truncated string. // Unit tests pass (@see Date.test.php) but I'm pretty sure this is not the right way to do it date_default_timezone_set($this->timezone); $dtzone = timezone_open('UTC'); $time = date('r', $this->timestamp); $dtime = date_create($time); date_timezone_set($dtime, $dtzone); $dateWithTimezone = date_format($dtime, 'r'); $dateWithoutTimezone = substr($dateWithTimezone, 0, -6); $timestamp = strtotime($dateWithoutTimezone); date_default_timezone_set('UTC'); return (int)$timestamp; } /** * Returns true if the current date is older than the given $date * * @param Piwik_Date $date * @return bool */ public function isLater( Piwik_Date $date) { return $this->getTimestamp() > $date->getTimestamp(); } /** * Returns true if the current date is earlier than the given $date * * @param Piwik_Date $date * @return bool */ public function isEarlier(Piwik_Date $date) { return $this->getTimestamp() < $date->getTimestamp(); } /** * Returns the Y-m-d representation of the string. * You can specify the output, see the list on php.net/date * * @param string $part * @return string */ public function toString($part = 'Y-m-d') { return date($part, $this->getTimestamp()); } /** * @see toString() * * @return string */ public function __toString() { return $this->toString(); } /** * Compares the week of the current date against the given $date * Returns 0 if equal, -1 if current week is earlier or 1 if current week is later * Example: 09.Jan.2007 13:07:25 -> compareWeek(2); -> 0 * * @param Piwik_Date $date * @return int 0 = equal, 1 = later, -1 = earlier */ public function compareWeek(Piwik_Date $date) { $currentWeek = date('W', $this->getTimestamp()); $toCompareWeek = date('W', $date->getTimestamp()); if( $currentWeek == $toCompareWeek) { return 0; } if( $currentWeek < $toCompareWeek) { return -1; } return 1; } /** * Compares the month of the current date against the given $date month * Returns 0 if equal, -1 if current month is earlier or 1 if current month is later * For example: 10.03.2000 -> 15.03.1950 -> 0 * * @param Piwik_Date $date Month to compare * @return int 0 = equal, 1 = later, -1 = earlier */ function compareMonth( Piwik_Date $date ) { $currentMonth = date('n', $this->getTimestamp()); $toCompareMonth = date('n', $date->getTimestamp()); if( $currentMonth == $toCompareMonth) { return 0; } if( $currentMonth < $toCompareMonth) { return -1; } return 1; } /** * Returns true if current date is today * * @return bool */ public function isToday() { return $this->toString('Y-m-d') === Piwik_Date::factory('today', $this->timezone)->toString('Y-m-d'); } /** * Returns a date object set to now (same as today, except that the time is also set) * * @return Piwik_Date */ static public function now() { return new Piwik_Date(time()); } /** * Returns a date object set to today midnight * * @return Piwik_Date */ static public function today() { return new Piwik_Date(strtotime(date("Y-m-d 00:00:00"))); } /** * Returns a date object set to yesterday midnight * * @return Piwik_Date */ static public function yesterday() { return new Piwik_Date(strtotime("yesterday")); } /** * Returns a date object set to yesterday same time of day * * @return Piwik_Date */ static public function yesterdaySameTime() { return new Piwik_Date(strtotime("yesterday ".date('H:i:s'))); } /** * Sets the time part of the date * Doesn't modify $this * * @param string $time HH:MM:SS * @return Piwik_Date The new date with the time part set */ public function setTime($time) { return new Piwik_Date( strtotime( date("Y-m-d", $this->timestamp) . " $time"), $this->timezone); } /** * Sets a new day * Returned is the new date object * Doesn't modify $this * * @param int $day Day eg. 31 * @return Piwik_Date new date */ public function setDay( $day ) { $ts = $this->timestamp; $result = mktime( date('H', $ts), date('i', $ts), date('s', $ts), date('n', $ts), $day, date('Y', $ts) ); return new Piwik_Date( $result, $this->timezone ); } /** * Sets a new year * Returned is the new date object * Doesn't modify $this * * @param int $year 2010 * @return Piwik_Date new date */ public function setYear( $year ) { $ts = $this->timestamp; $result = mktime( date('H', $ts), date('i', $ts), date('s', $ts), date('n', $ts), date('j', $ts), $year ); return new Piwik_Date( $result, $this->timezone ); } /** * Subtracts days from the existing date object and returns a new Piwik_Date object * Returned is the new date object * Doesn't modify $this * * @param int $n * @return Piwik_Date new date */ public function subDay( $n ) { if($n === 0) { return clone $this; } $ts = strtotime("-$n day", $this->timestamp); return new Piwik_Date( $ts, $this->timezone ); } /** * Subtracts weeks from the existing date object and returns a new Piwik_Date object * Returned is the new date object * Doesn't modify $this * * @param int $n * @return Piwik_Date new date */ public function subWeek( $n ) { return $this->subDay( 7 * $n ); } /** * Subtracts a month from the existing date object. * Returned is the new date object * Doesn't modify $this * * @param int $n * @return Piwik_Date new date */ public function subMonth( $n ) { if($n === 0) { return clone $this; } $ts = $this->timestamp; $result = mktime( date('H', $ts), date('i', $ts), date('s', $ts), date('n', $ts) - $n, 1, // we set the day to 1 date('Y', $ts) ); return new Piwik_Date( $result, $this->timezone ); } /** * Subtracts a year from the existing date object. * Returned is the new date object * Doesn't modify $this * * @param int $n * @return Piwik_Date new date */ public function subYear( $n ) { if($n === 0) { return clone $this; } $ts = $this->timestamp; $result = mktime( date('H', $ts), date('i', $ts), date('s', $ts), 1, // we set the month to 1 1, // we set the day to 1 date('Y', $ts) - $n ); return new Piwik_Date( $result, $this->timezone ); } /** * Returns a localized date string, given a template. * Allowed tags are: %day%, %shortDay%, %longDay%, etc. * * @param string $template string eg. %shortMonth% %longYear% * @return string eg. "Aug 2009" */ public function getLocalized($template) { $day = $this->toString('j'); $dayOfWeek = $this->toString('N'); $monthOfYear = $this->toString('n'); $patternToValue = array( "%day%" => $day, "%shortMonth%" => Piwik_Translate('General_ShortMonth_'.$monthOfYear), "%longMonth%" => Piwik_Translate('General_LongMonth_'.$monthOfYear), "%shortDay%" => Piwik_Translate('General_ShortDay_'.$dayOfWeek), "%longDay%" => Piwik_Translate('General_LongDay_'.$dayOfWeek), "%longYear%" => $this->toString('Y'), "%shortYear%" => $this->toString('y'), "%time%" => $this->toString('H:i:s') ); $out = str_replace(array_keys($patternToValue), array_values($patternToValue), $template); return $out; } /** * Adds days to the existing date object. * Returned is the new date object * Doesn't modify $this * * @param int $n Number of days to add * @return Piwik_Date new date */ public function addDay( $n ) { $ts = strtotime("+$n day", $this->timestamp); return new Piwik_Date( $ts, $this->timezone ); } /** * Adds hours to the existing date object. * Returned is the new date object * Doesn't modify $this * * @param int $n Number of hours to add * @return Piwik_Date new date */ public function addHour( $n ) { $ts = self::addHourTo( $this->timestamp, $n ); return new Piwik_Date( $ts, $this->timezone ); } /** * Adds N number of hours to a UNIX timestamp and returns the result. * * @param int $timestamp The timestamp to add to. * @param number $n Number of hours to add. * @return int The result as a UNIX timestamp. */ public static function addHourTo( $timestamp, $n ) { $isNegative = ($n < 0); $minutes = 0; if($n != round($n)) { if($n >= 1 || $n <= -1) { $extraMinutes = floor(abs($n)); if($isNegative) { $extraMinutes = -$extraMinutes; } $minutes = abs($n - $extraMinutes) * 60; if($isNegative) { $minutes *= -1; } } else { $minutes = $n * 60; } $n = floor(abs($n)); if($isNegative) { $n *= -1; } } return (int)($timestamp + round($minutes * 60) + $n * 3600); } /** * Substract hour to the existing date object. * Returned is the new date object * Doesn't modify $this * * @param int $n Number of hours to substract * @return Piwik_Date new date */ public function subHour( $n ) { return $this->addHour(-$n); } /** * Adds period to the existing date object. * Returned is the new date object * Doesn't modify $this * * @param int $n * @param string $period period to add (WEEK, DAY,...) * @return Piwik_Date new date */ public function addPeriod( $n, $period ) { if($n < 0) { $ts = strtotime("$n $period", $this->timestamp); } else { $ts = strtotime("+$n $period", $this->timestamp); } return new Piwik_Date( $ts, $this->timezone ); } /** * Subtracts period from the existing date object. * Returned is the new date object * Doesn't modify $this * * @param int $n * @param string $period period to sub * @return Piwik_Date new date */ public function subPeriod( $n, $period ) { return $this->addPeriod(-$n, $period ); } }