"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", ); 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 * @param string $time * @param DateTimeZone $timezone * @return SugarDateTime * @see DateTime::createFromFormat */ public static function createFromFormat($format, $time, DateTimeZone $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->getTimestamp()); $sd->setTimezone($d->getTimezone()); return $sd; } 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 strings * @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 */ 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->getTimestamp(); 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 */ public function __call($name, $args) { // fill in 5.2.x gaps if($name == "getTimestamp") { return (int)$this->format('U'); } 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; } function get_hour_end_time() { $newdate = clone $this; $newdate->setTime($this->hour, 59, 59); return $newdate; } function get_day_end_time() { $newdate = clone $this; return $newdate->setTime(23, 59, 59); } 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; } 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; } 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; } /** * Display as DB date * @return string */ function get_mysql_date() { return $this->format(TimeDate::DB_DATE_FORMAT); } /** * 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 */ 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->get_mysql_date() . ":" . $new_time->hour; $new_time->modify("next hour"); } else { $hash_list[] = $new_time->get_mysql_date(); $new_time->modify("next day"); } } return $hash_list; } /** * Get the beginning of the given day */ 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 timestamp of the given day */ 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; } function get_year_begin($year) { $newdate = clone $this; $newdate->setDate($this->year, 1, 1); $newdate->setTime(0,0); return $newdate; } /* * Print datetime in standard DB format * * Set $tz parameter to false if you are sure if 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); } /** * Get query string for the date * @return string */ function get_date_str() { return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour); } function __toString() { return $this->format('r'); } 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. * * @param string $format * @param string $time * @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() * @return SugarDateTime */ public function setDate ($year, $month, $day) { parent::setDate($year, $month, $day); return $this; } /** * (non-PHPdoc) * @see DateTime::setTime() * @return SugarDateTime */ public function setTime($hour, $minute, $sec = 0) { parent::setTime($hour, $minute, $sec); return $this; } /** * (non-PHPdoc) * @see DateTime::modify() * @return SugarDateTime */ public function modify($modify) { 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() * @return SugarDateTime */ public function setTimezone (DateTimeZone $timezone) { parent::setTimezone($timezone); return $this; } }