"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; } }