Permalink
Fetching contributors…
Cannot retrieve contributors at this time
690 lines (638 sloc) 19.4 KB
<?php
/*********************************************************************************
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
* Free Software Foundation with the addition of the following permission added
* to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
* IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
* OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along with
* this program; if not, see http://www.gnu.org/licenses or write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*
* You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
* SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "Powered by
* SugarCRM" logo. If the display of the logo is not reasonably feasible for
* technical reasons, the Appropriate Legal Notices must display the words
* "Powered by SugarCRM".
********************************************************************************/
/**
* Sugar DateTime container
* Extends regular PHP DateTime with useful services
* @api
*/
class SugarDateTime extends DateTime
{
// Recognized properties and their formats
protected $formats = array(
"sec" => "s",
"min" => "i",
"hour" => "G",
"zhour" => "H",
"day" => "j",
"zday" => "d",
"days_in_month" => "t",
"day_of_week" => "w",
"day_of_year" => "z",
"week" => "W",
"month" => "n",
"zmonth" => "m",
"year" => "Y",
"am_pm" => "A",
"hour_12" => "g",
);
// Property aliases
protected $var_gets = array(
"24_hour" => "hour",
"day_of_week" => "day_of_week_long",
"day_of_week_short" => "day_of_week_short",
"month_name" => "month_long",
"hour" => "hour_12",
);
/**
* @var DateTimeZone
*/
protected static $_gmt;
/**
* Calendar strings
* @var array
*/
protected $_strings;
/**
* For testing - if we allowed to use PHP date parse
* @var bool
*/
public static $use_php_parser = true;
/**
* For testing - if we allowed to use strptime()
* @var bool
*/
public static $use_strptime = true;
/**
* Copy of DateTime::createFromFormat
*
* Needed to return right type of the object
*
* @param string $format Format like in date()
* @param string $time Time to parse
* @param DateTimeZone $timezone
* @return SugarDateTime
* @see DateTime::createFromFormat
*/
public static function createFromFormat($format, $time, $timezone = null)
{
if(empty($time) || empty($format)) {
return false;
}
if(self::$use_php_parser && is_callable(array("DateTime", "createFromFormat"))) {
// 5.3, hurray!
if(!empty($timezone)) {
$d = parent::createFromFormat($format, $time, $timezone);
} else {
$d = parent::createFromFormat($format, $time);
}
} else {
// doh, 5.2, will have to simulate
$d = self::_createFromFormat($format, $time, $timezone);
}
if(!$d) {
return false;
}
$sd = new self($d->format(DateTime::ISO8601));
$sd->setTimezone($d->getTimezone());
return $sd;
}
/**
* Internal _createFromFormat implementation for 5.2
* @internal
* @param string $format Format like in date()
* @param string $time Time string to parse
* @param DateTimeZone $timezone TZ
* @return SugarDateTime
* @see DateTime::createFromFormat
*/
protected static function _createFromFormat($format, $time, DateTimeZone $timezone = null)
{
$res = new self();
if(!empty($timezone)) {
$res->setTimezone($timezone);
}
if(self::$use_strptime && function_exists("strptime")) {
$str_format = str_replace(array_keys(TimeDate::$format_to_str), array_values(TimeDate::$format_to_str), $format);
// for a reason unknown to modern science, %P doesn't work in strptime
$str_format = str_replace("%P", "%p", $str_format);
// strip spaces before am/pm as our formats don't have them
$time = preg_replace('/\s+(AM|PM)/i', '\1', $time);
// TODO: better way to not risk locale stuff problems?
$data = strptime($time, $str_format);
if(empty($data)) {
$GLOBALS['log']->error("Cannot parse $time for format $format");
return null;
}
if($data["tm_year"] == 0) {
unset($data["tm_year"]);
}
if($data["tm_mday"] == 0) {
unset($data["tm_mday"]);
}
if(isset($data["tm_year"])) {
$data["tm_year"] += 1900;
}
if(isset($data["tm_mon"])) {
$data["tm_mon"]++;
}
$data += self::$data_init; // fill in missing parts
} else {
// Windows, etc. might not have strptime - we'd have to work harder here
$data = $res->_strptime($time, $format);
}
if(empty($data)) {
$GLOBALS['log']->error("Cannot parse $time for format $format");
return null;
}
if(isset($data["tm_year"])) {
$res->setDate($data["tm_year"], $data["tm_mon"], $data["tm_mday"]);
}
$res->setTime($data["tm_hour"], $data["tm_min"], $data["tm_sec"]);
return $res;
}
/**
* Load language Calendar strings
* @internal
* @param string $name string section to return
* @return array
*/
protected function _getStrings($name)
{
if(empty($this->_strings)) {
$this->_strings = return_mod_list_strings_language($GLOBALS['current_language'],"Calendar");
}
return $this->_strings[$name];
}
/**
* Fetch property of the date by name
* @param string $var Property name
* @return mixed
*/
public function __get($var)
{
// simple formats
if(isset($this->formats[$var])) {
return $this->format($this->formats[$var]);
}
// conditional, derived and translated ones
switch($var) {
case "ts":
return $this->format("U")+0;
case "tz_offset":
return $this->getTimezone()->getOffset($this);
case "days_in_year":
return $this->format("L") == '1'?366:365;
break;
case "day_of_week_short":
$str = $this->_getStrings('dom_cal_weekdays');
return $str[$this->day_of_week];
case "day_of_week_long":
$str = $this->_getStrings('dom_cal_weekdays_long');
return $str[$this->day_of_week];
case "month_short":
$str = $this->_getStrings('dom_cal_month');
return $str[$this->month];
case "month_long":
$str = $this->_getStrings('dom_cal_month_long');
return $str[$this->month];
}
return '';
}
/**
* Implement some get_ methods that fetch variables
*
* @param string $name
* @param array $args
* @return mixed
*/
public function __call($name, $args)
{
// fill in 5.2.x gaps
if($name == "getTimestamp") {
return $this->format('U')+0;
}
if($name == "setTimestamp") {
$sec = (int)$args[0];
$sd = new self("@$sec");
$sd->setTimezone($this->getTimezone());
return $sd;
}
// getters
if(substr($name, 0, 4) == "get_") {
$var = substr($name, 4);
if(isset($this->var_gets[$var])) {
return $this->__get($this->var_gets[$var]);
}
if(isset($this->formats[$var])) {
return $this->__get($var);
}
}
$GLOBALS['log']->fatal("SugarDateTime: unknowm method $name called");
sugar_die("SugarDateTime: unknowm method $name called");
return false;
}
/**
* Get specific hour of today
* @param int $hour_index
* @return SugarDateTime
*/
public function get_datetime_by_index_today($hour_index)
{
if ( $hour_index < 0 || $hour_index > 23 )
{
sugar_die("hour is outside of range");
}
$newdate = clone $this;
$newdate->setTime($hour_index, 0, 0);
return $newdate;
}
/**
* Get the last second of current hour
* @return SugarDateTime
*/
function get_hour_end_time()
{
$newdate = clone $this;
$newdate->setTime($this->hour, 59, 59);
return $newdate;
}
/**
* Get the last second of the current day
* @return SugarDateTime
*/
function get_day_end_time()
{
$newdate = clone $this;
return $newdate->setTime(23, 59, 59);
}
/**
* Get the beginning of i's day of the week
* @param int $day_index Day, 0 is Sunday, 1 is Monday, etc.
* @return SugarDateTime
*/
function get_day_by_index_this_week($day_index)
{
$newdate = clone $this;
$newdate->setDate($this->year, $this->month, $this->day +
($day_index - $this->day_of_week))->setTime(0,0);
return $newdate;
}
/**
* Get the beginning of the last day of i's the month
* @deprecated
* FIXME: no idea why this function exists and what's the use of it
* @param int $month_index Month, January is 0
* @return SugarDateTime
*/
function get_day_by_index_this_year($month_index)
{
$newdate = clone $this;
$newdate->setDate($this->year, $month_index+1, 1);
$newdate->setDate($newdate->year, $newdate->month, $newdate->days_in_month);
$newdate->setTime(0, 0);
return $newdate;
}
/**
* Get the beginning of i's day of the month
* @param int $day_index 0 is the first day of the month (sic!)
* @return SugarDateTime
*/
function get_day_by_index_this_month($day_index)
{
$newdate = clone $this;
return $newdate->setDate($this->year, $this->month, $day_index+1)->setTime(0, 0);
}
/**
* Get new date, modified by date expression
*
* @example $yesterday = $today->get("yesterday");
*
* @param string $expression
* @return SugarDateTime
*/
function get($expression)
{
$newdate = clone $this;
$newdate->modify($expression);
return $newdate;
}
/**
* Create from ISO 8601 datetime
* @param string $str
* @return SugarDateTime
*/
static public function parse_utc_date_time($str)
{
return new self($str);
}
/**
* Create a list of time slots for calendar view
* Times must be in user TZ
* @param string $view Which view we are using - day, week, month
* @param SugarDateTime $start_time Start time
* @param SugarDateTime $end_time End time
* @return array
*/
static function getHashList($view, $start_time, $end_time)
{
$hash_list = array();
if ( $view != 'day')
{
$end_time = $end_time->get_day_end_time();
}
$end = $end_time->ts;
if($end <= $start_time->ts) {
$end = $start_time->ts+1;
}
$new_time = clone $start_time;
$new_time->setTime($new_time->hour, 0, 0);
while ($new_time->ts < $end) {
if ($view == 'day') {
$hash_list[] = $new_time->format(TimeDate::DB_DATE_FORMAT) . ":" . $new_time->hour;
$new_time->modify("next hour");
} else {
$hash_list[] = $new_time->format(TimeDate::DB_DATE_FORMAT);
$new_time->modify("next day");
}
}
return $hash_list;
}
/**
* Get the beginning of the given day
* @param int $day Day, starting with 1, default is current
* @param int $month Month, starting with 1, default is current
* @param int $year Year, default is current
* @return SugarDateTime
*/
function get_day_begin($day = null, $month = null, $year = null)
{
$newdate = clone $this;
$newdate->setDate(
$year?$year:$this->year,
$month?$month:$this->month,
$day?$day:$this->day);
$newdate->setTime(0, 0);
return $newdate;
}
/**
* Get the last second of the given day
* @param int $day Day, starting with 1, default is current
* @param int $month Month, starting with 1, default is current
* @param int $year Year, default is current
* @return SugarDateTime
*/
function get_day_end($day = null, $month = null, $year = null)
{
$newdate = clone $this;
$newdate->setDate(
$year?$year:$this->year,
$month?$month:$this->month,
$day?$day:$this->day);
$newdate->setTime(23, 59, 59);
return $newdate;
}
/**
* Get the beginning of the first day of the year
* @param int $year
* @return SugarDateTime
*/
function get_year_begin($year)
{
$newdate = clone $this;
$newdate->setDate($year, 1, 1);
$newdate->setTime(0,0);
return $newdate;
}
/**
* Print datetime in standard DB format
*
* Set $tz parameter to false if you are sure that the date is in UTC.
*
* @param bool $tz do conversion to UTC
* @return string
*/
function asDb($tz = true)
{
if($tz) {
if(empty(self::$_gmt)) {
self::$_gmt = new DateTimeZone("UTC");
}
$this->setTimezone(self::$_gmt);
}
return $this->format(TimeDate::DB_DATETIME_FORMAT);
}
/**
* Print date in standard DB format
*
* Set $tz parameter to false if you are sure that the date is in UTC.
*
* @param bool $tz do conversion to UTC
* @return string
*/
function asDbDate($tz = true)
{
if($tz) {
if(empty(self::$_gmt)) {
self::$_gmt = new DateTimeZone("UTC");
}
$this->setTimezone(self::$_gmt);
}
return $this->format(TimeDate::DB_DATE_FORMAT);
}
/**
* Get query string for the date, year=%d&month=%d&day=%d&hour=%d
* @return string
*/
function get_date_str()
{
return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour);
}
/**
* Convert date to string - 'r' format, like: Thu, 21 Dec 2000 16:01:07 +0200
* @return string
*/
function __toString()
{
return $this->format('r');
}
/**
* Match between tm_ parts and date() format strings
* @var array
*/
protected static $parts_match = array(
'Y' => 'tm_year',
'm' => 'tm_mon',
'n' => 'tm_mon',
'd' => 'tm_mday',
'H' => 'tm_hour',
'h' => 'tm_hour',
'i' => 'tm_min',
's' => 'tm_sec',
);
protected static $data_init = array(
"tm_hour" => 0,
"tm_min" => 0,
"tm_sec" => 0,
);
protected static $strptime_short_mon, $strptime_long_mon;
/**
* DateTime homebrew parser
*
* Since some OSes and PHP versions (please upgrade to 5.3!) do not support built-in parsing functions,
* we have to restort to this ugliness.
* @internal
* @param string $time Time formatted string
* @param string $format Format, as accepted by strptime()
* @return array Parsed parts
*/
protected function _strptime($time, $format)
{
$data = self::$data_init;
if(empty(self::$strptime_short_mon)) {
self::$strptime_short_mon = array_flip($this->_getStrings('dom_cal_month'));
unset(self::$strptime_short_mon[""]);
}
if(empty(self::$strptime_long_mon)) {
self::$strptime_long_mon = array_flip($this->_getStrings('dom_cal_month_long'));
unset(self::$strptime_long_mon[""]);
}
$regexp = TimeDate::get_regular_expression($format);
if(!preg_match('@'.$regexp['format'].'@', $time, $dateparts)) {
return false;
}
foreach(self::$parts_match as $part => $datapart) {
if (isset($regexp['positions'][$part]) && isset($dateparts[$regexp['positions'][$part]])) {
$data[$datapart] = (int)$dateparts[$regexp['positions'][$part]];
}
}
// now process non-numeric ones
if ( isset($regexp['positions']['F']) && !empty($dateparts[$regexp['positions']['F']])) {
// FIXME: locale?
$mon = $dateparts[$regexp['positions']['F']];
if(isset(self::$sugar_strptime_long_mon[$mon])) {
$data["tm_mon"] = self::$sugar_strptime_long_mon[$mon];
} else {
return false;
}
}
if ( isset($regexp['positions']['M']) && !empty($dateparts[$regexp['positions']['M']])) {
// FIXME: locale?
$mon = $dateparts[$regexp['positions']['M']];
if(isset(self::$sugar_strptime_short_mon[$mon])) {
$data["tm_mon"] = self::$sugar_strptime_short_mon[$mon];
} else {
return false;
}
}
if ( isset($regexp['positions']['a']) && !empty($dateparts[$regexp['positions']['a']])) {
$ampm = trim($dateparts[$regexp['positions']['a']]);
if($ampm == 'pm') {
if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
} else if($ampm == 'am') {
if($data["tm_hour"] == 12) {
// 12:00am is 00:00
$data["tm_hour"] = 0;
}
} else {
return false;
}
}
if ( isset($regexp['positions']['A']) && !empty($dateparts[$regexp['positions']['A']])) {
$ampm = trim($dateparts[$regexp['positions']['A']]);
if($ampm == 'PM') {
if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
} else if($ampm == 'AM') {
if($data["tm_hour"] == 12) {
// 12:00am is 00:00
$data["tm_hour"] = 0;
}
} else {
return false;
}
}
return $data;
}
// 5.2 compatibility - 5.2 functions don't return $this, let's help them
/**
* (non-PHPdoc)
* @see DateTime::setDate()
* @param $year
* @param $month
* @param $day
* @return SugarDateTime
*/
public function setDate ($year, $month, $day)
{
parent::setDate($year, $month, $day);
return $this;
}
/**
* (non-PHPdoc)
* @see DateTime::setTime()
* @param $hour
* @param $minute
* @param int $sec
* @return SugarDateTime
*/
public function setTime($hour, $minute, $sec = 0)
{
parent::setTime($hour, $minute, $sec);
return $this;
}
/**
* (non-PHPdoc)
* @see DateTime::modify()
* @param $modify
* @return SugarDateTime
*/
public function modify($modify)
{
// We can't user PHP_VERSION_ID here because problem with yesterday and tomorrow appears in defferent versions
// In that case we just set time to midnight for yesterday & tomorrow
// To leave time as it is we can use -+1 day instead of yesterday & tomorrow
if (strpos($modify, 'yesterday') !== false || strpos($modify, 'tomorrow') !== false) {
$this->setTime(0, 0);
}
if(PHP_VERSION_ID >= 50300 || $modify != 'first day of next month') {
parent::modify($modify);
} else {
/* PHP 5.2 does not understand 'first day of' and defaults need it */
$this->setDate($this->year, $this->month+1, 1);
}
return $this;
}
/**
* (non-PHPdoc)
* @see DateTime::setTimezone()
* @param DateTimeZone $timezone
* @return SugarDateTime
*/
public function setTimezone ($timezone)
{
parent::setTimezone($timezone);
return $this;
}
}