2 /*********************************************************************************
3 * SugarCRM Community Edition is a customer relationship management program developed by
4 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6 * This program is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU Affero General Public License version 3 as published by the
8 * Free Software Foundation with the addition of the following permission added
9 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
10 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
11 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13 * This program is distributed in the hope that it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
18 * You should have received a copy of the GNU Affero General Public License along with
19 * this program; if not, see http://www.gnu.org/licenses or write to the Free
20 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
24 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26 * The interactive user interfaces in modified source and object code versions
27 * of this program must display Appropriate Legal Notices, as required under
28 * Section 5 of the GNU Affero General Public License version 3.
30 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
31 * these Appropriate Legal Notices must retain the display of the "Powered by
32 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
33 * technical reasons, the Appropriate Legal Notices must display the words
34 * "Powered by SugarCRM".
35 ********************************************************************************/
38 class SugarDateTime extends DateTime
40 protected $formats = array(
47 "days_in_month" => "t",
58 protected $var_gets = array(
60 "day_of_week" => "day_of_week_long",
61 "day_of_week_short" => "day_of_week_short",
62 "month_name" => "month_long",
69 protected static $_gmt;
78 * For testing - if we allowed to use PHP date parse
81 public static $use_php_parser = true;
84 * For testing - if we allowed to use strptime()
87 public static $use_strptime = true;
90 * Copy of DateTime::createFromFormat
92 * Needed to return right type of the object
94 * @param string $format Format like in date()
95 * @param string $time Time to parse
96 * @param DateTimeZone $timezone
97 * @return SugarDateTime
98 * @see DateTime::createFromFormat
100 public static function createFromFormat($format, $time, $timezone = null)
102 if(empty($time) || empty($format)) {
105 if(self::$use_php_parser && is_callable(array("DateTime", "createFromFormat"))) {
107 if(!empty($timezone)) {
108 $d = parent::createFromFormat($format, $time, $timezone);
110 $d = parent::createFromFormat($format, $time);
113 // doh, 5.2, will have to simulate
114 $d = self::_createFromFormat($format, $time, $timezone);
119 $sd = new self($d->format(DateTime::ISO8601));
120 $sd->setTimezone($d->getTimezone());
125 * Internal _createFromFormat implementation for 5.2
126 * @param string $format Format like in date()
127 * @param string $time Time string to parse
128 * @param DateTimeZone $timezone TZ
129 * @see DateTime::createFromFormat
131 protected static function _createFromFormat($format, $time, DateTimeZone $timezone = null)
134 if(!empty($timezone)) {
135 $res->setTimezone($timezone);
137 if(self::$use_strptime && function_exists("strptime")) {
138 $str_format = str_replace(array_keys(TimeDate::$format_to_str), array_values(TimeDate::$format_to_str), $format);
139 // for a reason unknown to modern science, %P doesn't work in strptime
140 $str_format = str_replace("%P", "%p", $str_format);
141 // strip spaces before am/pm as our formats don't have them
142 $time = preg_replace('/\s+(AM|PM)/i', '\1', $time);
143 // TODO: better way to not risk locale stuff problems?
144 $data = strptime($time, $str_format);
146 $GLOBALS['log']->error("Cannot parse $time for format $format");
149 if($data["tm_year"] == 0) {
150 unset($data["tm_year"]);
152 if($data["tm_mday"] == 0) {
153 unset($data["tm_mday"]);
155 if(isset($data["tm_year"])) {
156 $data["tm_year"] += 1900;
158 if(isset($data["tm_mon"])) {
161 $data += self::$data_init; // fill in missing parts
163 // Windows, etc. might not have strptime - we'd have to work harder here
164 $data = $res->_strptime($time, $format);
167 $GLOBALS['log']->error("Cannot parse $time for format $format");
170 if(isset($data["tm_year"])) {
171 $res->setDate($data["tm_year"], $data["tm_mon"], $data["tm_mday"]);
173 $res->setTime($data["tm_hour"], $data["tm_min"], $data["tm_sec"]);
178 * Load language strings
179 * @param string $name string section to return
182 protected function _getStrings($name)
184 if(empty($this->_strings)) {
185 $this->_strings = return_mod_list_strings_language($GLOBALS['current_language'],"Calendar");
187 return $this->_strings[$name];
191 * Fetch property of the date by name
192 * @param string $var Property name
194 public function __get($var)
197 if(isset($this->formats[$var])) {
198 return $this->format($this->formats[$var]);
200 // conditional, derived and translated ones
203 return $this->getTimestamp();
205 return $this->getTimezone()->getOffset($this);
207 return $this->format("L") == '1'?366:365;
209 case "day_of_week_short":
210 $str = $this->_getStrings('dom_cal_weekdays');
211 return $str[$this->day_of_week];
212 case "day_of_week_long":
213 $str = $this->_getStrings('dom_cal_weekdays_long');
214 return $str[$this->day_of_week];
216 $str = $this->_getStrings('dom_cal_month');
217 return $str[$this->month];
219 $str = $this->_getStrings('dom_cal_month_long');
220 return $str[$this->month];
227 * Implement some get_ methods that fetch variables
229 * @param string $name
232 public function __call($name, $args)
234 // fill in 5.2.x gaps
235 if($name == "getTimestamp") {
236 return (int)$this->format('U');
238 if($name == "setTimestamp") {
239 $sec = (int)$args[0];
240 $sd = new self("@$sec");
241 $sd->setTimezone($this->getTimezone());
246 if(substr($name, 0, 4) == "get_") {
247 $var = substr($name, 4);
249 if(isset($this->var_gets[$var])) {
250 return $this->__get($this->var_gets[$var]);
253 if(isset($this->formats[$var])) {
254 return $this->__get($var);
257 $GLOBALS['log']->fatal("SugarDateTime: unknowm method $name called");
258 sugar_die("SugarDateTime: unknowm method $name called");
263 * Get specific hour of today
264 * @param int $hour_index
265 * @return SugarDateTime
267 public function get_datetime_by_index_today($hour_index)
269 if ( $hour_index < 0 || $hour_index > 23 )
271 sugar_die("hour is outside of range");
274 $newdate = clone $this;
275 $newdate->setTime($hour_index, 0, 0);
279 function get_hour_end_time()
281 $newdate = clone $this;
282 $newdate->setTime($this->hour, 59, 59);
286 function get_day_end_time()
288 $newdate = clone $this;
289 return $newdate->setTime(23, 59, 59);
292 function get_day_by_index_this_week($day_index)
294 $newdate = clone $this;
295 $newdate->setDate($this->year, $this->month, $this->day +
296 ($day_index - $this->day_of_week))->setTime(0,0);
300 function get_day_by_index_this_year($month_index)
302 $newdate = clone $this;
303 $newdate->setDate($this->year, $month_index+1, 1);
304 $newdate->setDate($newdate->year, $newdate->month, $newdate->days_in_month);
305 $newdate->setTime(0, 0);
309 function get_day_by_index_this_month($day_index)
311 $newdate = clone $this;
312 return $newdate->setDate($this->year, $this->month, $day_index+1)->setTime(0, 0);
316 * Get new date, modified by date expression
318 * @example $yesterday = $today->get("yesterday");
320 * @param string $expression
321 * @return SugarDateTime
323 function get($expression)
325 $newdate = clone $this;
326 $newdate->modify($expression);
334 function get_mysql_date()
336 return $this->format(TimeDate::DB_DATE_FORMAT);
340 * Create from ISO 8601 datetime
342 * @return SugarDateTime
344 static public function parse_utc_date_time($str)
346 return new self($str);
350 * Create a list of time slots for calendar view
351 * Times must be in user TZ
352 * @param string $view Which view we are using - day, week, month
353 * @param SugarDateTime $start_time Start time
354 * @param SugarDateTime $end_time End time
356 static function getHashList($view, $start_time, $end_time)
358 $hash_list = array();
362 $end_time = $end_time->get_day_end_time();
365 $end = $end_time->ts;
366 if($end <= $start_time->ts) {
367 $end = $start_time->ts+1;
370 $new_time = clone $start_time;
371 $new_time->setTime($new_time->hour, 0, 0);
373 while ($new_time->ts < $end) {
374 if ($view == 'day') {
375 $hash_list[] = $new_time->get_mysql_date() . ":" . $new_time->hour;
376 $new_time->modify("next hour");
378 $hash_list[] = $new_time->get_mysql_date();
379 $new_time->modify("next day");
387 * Get the beginning of the given day
389 function get_day_begin($day = null, $month = null, $year = null)
391 $newdate = clone $this;
393 $year?$year:$this->year,
394 $month?$month:$this->month,
395 $day?$day:$this->day);
396 $newdate->setTime(0, 0);
401 * Get the last timestamp of the given day
403 function get_day_end($day = null, $month = null, $year = null)
405 $newdate = clone $this;
407 $year?$year:$this->year,
408 $month?$month:$this->month,
409 $day?$day:$this->day);
410 $newdate->setTime(23, 59, 59);
414 function get_year_begin($year)
416 $newdate = clone $this;
417 $newdate->setDate($this->year, 1, 1);
418 $newdate->setTime(0,0);
423 * Print datetime in standard DB format
425 * Set $tz parameter to false if you are sure if the date is in UTC.
427 * @param bool $tz do conversion to UTC
430 function asDb($tz = true)
433 if(empty(self::$_gmt)) {
434 self::$_gmt = new DateTimeZone("UTC");
436 $this->setTimezone(self::$_gmt);
438 return $this->format(TimeDate::DB_DATETIME_FORMAT);
442 * Print date in standard DB format
444 * Set $tz parameter to false if you are sure if the date is in UTC.
446 * @param bool $tz do conversion to UTC
449 function asDbDate($tz = true)
452 if(empty(self::$_gmt)) {
453 self::$_gmt = new DateTimeZone("UTC");
455 $this->setTimezone(self::$_gmt);
457 return $this->format(TimeDate::DB_DATE_FORMAT);
461 * Get query string for the date
464 function get_date_str()
466 return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour);
469 function __toString()
471 return $this->format('r');
474 protected static $parts_match = array(
485 protected static $data_init = array(
491 protected static $strptime_short_mon, $strptime_long_mon;
493 * DateTime homebrew parser
495 * Since some OSes and PHP versions (please upgrade to 5.3!) do not support built-in parsing functions,
496 * we have to restort to this ugliness.
498 * @param string $format
499 * @param string $time
500 * @return array Parsed parts
502 protected function _strptime($time, $format)
504 $data = self::$data_init;
505 if(empty(self::$strptime_short_mon)) {
506 self::$strptime_short_mon = array_flip($this->_getStrings('dom_cal_month'));
507 unset(self::$strptime_short_mon[""]);
509 if(empty(self::$strptime_long_mon)) {
510 self::$strptime_long_mon = array_flip($this->_getStrings('dom_cal_month_long'));
511 unset(self::$strptime_long_mon[""]);
514 $regexp = TimeDate::get_regular_expression($format);
515 if(!preg_match('@'.$regexp['format'].'@', $time, $dateparts)) {
519 foreach(self::$parts_match as $part => $datapart) {
520 if (isset($regexp['positions'][$part]) && isset($dateparts[$regexp['positions'][$part]])) {
521 $data[$datapart] = (int)$dateparts[$regexp['positions'][$part]];
524 // now process non-numeric ones
525 if ( isset($regexp['positions']['F']) && !empty($dateparts[$regexp['positions']['F']])) {
527 $mon = $dateparts[$regexp['positions']['F']];
528 if(isset(self::$sugar_strptime_long_mon[$mon])) {
529 $data["tm_mon"] = self::$sugar_strptime_long_mon[$mon];
534 if ( isset($regexp['positions']['M']) && !empty($dateparts[$regexp['positions']['M']])) {
536 $mon = $dateparts[$regexp['positions']['M']];
537 if(isset(self::$sugar_strptime_short_mon[$mon])) {
538 $data["tm_mon"] = self::$sugar_strptime_short_mon[$mon];
543 if ( isset($regexp['positions']['a']) && !empty($dateparts[$regexp['positions']['a']])) {
544 $ampm = trim($dateparts[$regexp['positions']['a']]);
546 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
547 } else if($ampm == 'am') {
548 if($data["tm_hour"] == 12) {
550 $data["tm_hour"] = 0;
557 if ( isset($regexp['positions']['A']) && !empty($dateparts[$regexp['positions']['A']])) {
558 $ampm = trim($dateparts[$regexp['positions']['A']]);
560 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
561 } else if($ampm == 'AM') {
562 if($data["tm_hour"] == 12) {
564 $data["tm_hour"] = 0;
574 // 5.2 compatibility - 5.2 functions don't return $this, let's help them
578 * @see DateTime::setDate()
579 * @return SugarDateTime
581 public function setDate ($year, $month, $day)
583 parent::setDate($year, $month, $day);
589 * @see DateTime::setTime()
590 * @return SugarDateTime
592 public function setTime($hour, $minute, $sec = 0)
594 parent::setTime($hour, $minute, $sec);
600 * @see DateTime::modify()
601 * @return SugarDateTime
603 public function modify($modify)
605 if(PHP_VERSION_ID >= 50300 || $modify != 'first day of next month') {
606 parent::modify($modify);
608 /* PHP 5.2 does not understand 'first day of' and defaults need it */
609 $this->setDate($this->year, $this->month+1, 1);
616 * @see DateTime::setTimezone()
617 * @param DateTimeZone $timezone
618 * @return SugarDateTime
620 public function setTimezone ($timezone)
622 parent::setTimezone($timezone);