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 ********************************************************************************/
39 * Sugar DateTime container
40 * Extends regular PHP DateTime with useful services
43 class SugarDateTime extends DateTime
45 // Recognized properties and their formats
46 protected $formats = array(
53 "days_in_month" => "t",
65 protected $var_gets = array(
67 "day_of_week" => "day_of_week_long",
68 "day_of_week_short" => "day_of_week_short",
69 "month_name" => "month_long",
76 protected static $_gmt;
85 * For testing - if we allowed to use PHP date parse
88 public static $use_php_parser = true;
91 * For testing - if we allowed to use strptime()
94 public static $use_strptime = true;
97 * Copy of DateTime::createFromFormat
99 * Needed to return right type of the object
101 * @param string $format Format like in date()
102 * @param string $time Time to parse
103 * @param DateTimeZone $timezone
104 * @return SugarDateTime
105 * @see DateTime::createFromFormat
107 public static function createFromFormat($format, $time, $timezone = null)
109 if(empty($time) || empty($format)) {
112 if(self::$use_php_parser && is_callable(array("DateTime", "createFromFormat"))) {
114 if(!empty($timezone)) {
115 $d = parent::createFromFormat($format, $time, $timezone);
117 $d = parent::createFromFormat($format, $time);
120 // doh, 5.2, will have to simulate
121 $d = self::_createFromFormat($format, $time, $timezone);
126 $sd = new self($d->format(DateTime::ISO8601));
127 $sd->setTimezone($d->getTimezone());
132 * Internal _createFromFormat implementation for 5.2
134 * @param string $format Format like in date()
135 * @param string $time Time string to parse
136 * @param DateTimeZone $timezone TZ
137 * @return SugarDateTime
138 * @see DateTime::createFromFormat
140 protected static function _createFromFormat($format, $time, DateTimeZone $timezone = null)
143 if(!empty($timezone)) {
144 $res->setTimezone($timezone);
146 if(self::$use_strptime && function_exists("strptime")) {
147 $str_format = str_replace(array_keys(TimeDate::$format_to_str), array_values(TimeDate::$format_to_str), $format);
148 // for a reason unknown to modern science, %P doesn't work in strptime
149 $str_format = str_replace("%P", "%p", $str_format);
150 // strip spaces before am/pm as our formats don't have them
151 $time = preg_replace('/\s+(AM|PM)/i', '\1', $time);
152 // TODO: better way to not risk locale stuff problems?
153 $data = strptime($time, $str_format);
155 $GLOBALS['log']->error("Cannot parse $time for format $format");
158 if($data["tm_year"] == 0) {
159 unset($data["tm_year"]);
161 if($data["tm_mday"] == 0) {
162 unset($data["tm_mday"]);
164 if(isset($data["tm_year"])) {
165 $data["tm_year"] += 1900;
167 if(isset($data["tm_mon"])) {
170 $data += self::$data_init; // fill in missing parts
172 // Windows, etc. might not have strptime - we'd have to work harder here
173 $data = $res->_strptime($time, $format);
176 $GLOBALS['log']->error("Cannot parse $time for format $format");
179 if(isset($data["tm_year"])) {
180 $res->setDate($data["tm_year"], $data["tm_mon"], $data["tm_mday"]);
182 $res->setTime($data["tm_hour"], $data["tm_min"], $data["tm_sec"]);
187 * Load language Calendar strings
189 * @param string $name string section to return
192 protected function _getStrings($name)
194 if(empty($this->_strings)) {
195 $this->_strings = return_mod_list_strings_language($GLOBALS['current_language'],"Calendar");
197 return $this->_strings[$name];
201 * Fetch property of the date by name
202 * @param string $var Property name
205 public function __get($var)
208 if(isset($this->formats[$var])) {
209 return $this->format($this->formats[$var]);
211 // conditional, derived and translated ones
214 return $this->format("U")+0;
216 return $this->getTimezone()->getOffset($this);
218 return $this->format("L") == '1'?366:365;
220 case "day_of_week_short":
221 $str = $this->_getStrings('dom_cal_weekdays');
222 return $str[$this->day_of_week];
223 case "day_of_week_long":
224 $str = $this->_getStrings('dom_cal_weekdays_long');
225 return $str[$this->day_of_week];
227 $str = $this->_getStrings('dom_cal_month');
228 return $str[$this->month];
230 $str = $this->_getStrings('dom_cal_month_long');
231 return $str[$this->month];
238 * Implement some get_ methods that fetch variables
240 * @param string $name
244 public function __call($name, $args)
246 // fill in 5.2.x gaps
247 if($name == "getTimestamp") {
248 return $this->format('U')+0;
250 if($name == "setTimestamp") {
251 $sec = (int)$args[0];
252 $sd = new self("@$sec");
253 $sd->setTimezone($this->getTimezone());
258 if(substr($name, 0, 4) == "get_") {
259 $var = substr($name, 4);
261 if(isset($this->var_gets[$var])) {
262 return $this->__get($this->var_gets[$var]);
265 if(isset($this->formats[$var])) {
266 return $this->__get($var);
269 $GLOBALS['log']->fatal("SugarDateTime: unknowm method $name called");
270 sugar_die("SugarDateTime: unknowm method $name called");
275 * Get specific hour of today
276 * @param int $hour_index
277 * @return SugarDateTime
279 public function get_datetime_by_index_today($hour_index)
281 if ( $hour_index < 0 || $hour_index > 23 )
283 sugar_die("hour is outside of range");
286 $newdate = clone $this;
287 $newdate->setTime($hour_index, 0, 0);
292 * Get the last second of current hour
293 * @return SugarDateTime
295 function get_hour_end_time()
297 $newdate = clone $this;
298 $newdate->setTime($this->hour, 59, 59);
303 * Get the last second of the current day
304 * @return SugarDateTime
306 function get_day_end_time()
308 $newdate = clone $this;
309 return $newdate->setTime(23, 59, 59);
313 * Get the beginning of i's day of the week
314 * @param int $day_index Day, 0 is Sunday, 1 is Monday, etc.
315 * @return SugarDateTime
317 function get_day_by_index_this_week($day_index)
319 $newdate = clone $this;
320 $newdate->setDate($this->year, $this->month, $this->day +
321 ($day_index - $this->day_of_week))->setTime(0,0);
326 * Get the beginning of the last day of i's the month
328 * FIXME: no idea why this function exists and what's the use of it
329 * @param int $month_index Month, January is 0
330 * @return SugarDateTime
332 function get_day_by_index_this_year($month_index)
334 $newdate = clone $this;
335 $newdate->setDate($this->year, $month_index+1, 1);
336 $newdate->setDate($newdate->year, $newdate->month, $newdate->days_in_month);
337 $newdate->setTime(0, 0);
342 * Get the beginning of i's day of the month
343 * @param int $day_index 0 is the first day of the month (sic!)
344 * @return SugarDateTime
346 function get_day_by_index_this_month($day_index)
348 $newdate = clone $this;
349 return $newdate->setDate($this->year, $this->month, $day_index+1)->setTime(0, 0);
353 * Get new date, modified by date expression
355 * @example $yesterday = $today->get("yesterday");
357 * @param string $expression
358 * @return SugarDateTime
360 function get($expression)
362 $newdate = clone $this;
363 $newdate->modify($expression);
368 * Create from ISO 8601 datetime
370 * @return SugarDateTime
372 static public function parse_utc_date_time($str)
374 return new self($str);
378 * Create a list of time slots for calendar view
379 * Times must be in user TZ
380 * @param string $view Which view we are using - day, week, month
381 * @param SugarDateTime $start_time Start time
382 * @param SugarDateTime $end_time End time
385 static function getHashList($view, $start_time, $end_time)
387 $hash_list = array();
391 $end_time = $end_time->get_day_end_time();
394 $end = $end_time->ts;
395 if($end <= $start_time->ts) {
396 $end = $start_time->ts+1;
399 $new_time = clone $start_time;
400 $new_time->setTime($new_time->hour, 0, 0);
402 while ($new_time->ts < $end) {
403 if ($view == 'day') {
404 $hash_list[] = $new_time->format(TimeDate::DB_DATE_FORMAT) . ":" . $new_time->hour;
405 $new_time->modify("next hour");
407 $hash_list[] = $new_time->format(TimeDate::DB_DATE_FORMAT);
408 $new_time->modify("next day");
416 * Get the beginning of the given day
417 * @param int $day Day, starting with 1, default is current
418 * @param int $month Month, starting with 1, default is current
419 * @param int $year Year, default is current
420 * @return SugarDateTime
422 function get_day_begin($day = null, $month = null, $year = null)
424 $newdate = clone $this;
426 $year?$year:$this->year,
427 $month?$month:$this->month,
428 $day?$day:$this->day);
429 $newdate->setTime(0, 0);
434 * Get the last second of the given day
435 * @param int $day Day, starting with 1, default is current
436 * @param int $month Month, starting with 1, default is current
437 * @param int $year Year, default is current
438 * @return SugarDateTime
440 function get_day_end($day = null, $month = null, $year = null)
442 $newdate = clone $this;
444 $year?$year:$this->year,
445 $month?$month:$this->month,
446 $day?$day:$this->day);
447 $newdate->setTime(23, 59, 59);
452 * Get the beginning of the first day of the year
454 * @return SugarDateTime
456 function get_year_begin($year)
458 $newdate = clone $this;
459 $newdate->setDate($year, 1, 1);
460 $newdate->setTime(0,0);
465 * Print datetime in standard DB format
467 * Set $tz parameter to false if you are sure that the date is in UTC.
469 * @param bool $tz do conversion to UTC
472 function asDb($tz = true)
475 if(empty(self::$_gmt)) {
476 self::$_gmt = new DateTimeZone("UTC");
478 $this->setTimezone(self::$_gmt);
480 return $this->format(TimeDate::DB_DATETIME_FORMAT);
484 * Print date in standard DB format
486 * Set $tz parameter to false if you are sure that the date is in UTC.
488 * @param bool $tz do conversion to UTC
491 function asDbDate($tz = true)
494 if(empty(self::$_gmt)) {
495 self::$_gmt = new DateTimeZone("UTC");
497 $this->setTimezone(self::$_gmt);
499 return $this->format(TimeDate::DB_DATE_FORMAT);
503 * Get query string for the date, year=%d&month=%d&day=%d&hour=%d
506 function get_date_str()
508 return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour);
512 * Convert date to string - 'r' format, like: Thu, 21 Dec 2000 16:01:07 +0200
515 function __toString()
517 return $this->format('r');
521 * Match between tm_ parts and date() format strings
524 protected static $parts_match = array(
535 protected static $data_init = array(
541 protected static $strptime_short_mon, $strptime_long_mon;
543 * DateTime homebrew parser
545 * Since some OSes and PHP versions (please upgrade to 5.3!) do not support built-in parsing functions,
546 * we have to restort to this ugliness.
548 * @param string $time Time formatted string
549 * @param string $format Format, as accepted by strptime()
550 * @return array Parsed parts
552 protected function _strptime($time, $format)
554 $data = self::$data_init;
555 if(empty(self::$strptime_short_mon)) {
556 self::$strptime_short_mon = array_flip($this->_getStrings('dom_cal_month'));
557 unset(self::$strptime_short_mon[""]);
559 if(empty(self::$strptime_long_mon)) {
560 self::$strptime_long_mon = array_flip($this->_getStrings('dom_cal_month_long'));
561 unset(self::$strptime_long_mon[""]);
564 $regexp = TimeDate::get_regular_expression($format);
565 if(!preg_match('@'.$regexp['format'].'@', $time, $dateparts)) {
569 foreach(self::$parts_match as $part => $datapart) {
570 if (isset($regexp['positions'][$part]) && isset($dateparts[$regexp['positions'][$part]])) {
571 $data[$datapart] = (int)$dateparts[$regexp['positions'][$part]];
574 // now process non-numeric ones
575 if ( isset($regexp['positions']['F']) && !empty($dateparts[$regexp['positions']['F']])) {
577 $mon = $dateparts[$regexp['positions']['F']];
578 if(isset(self::$sugar_strptime_long_mon[$mon])) {
579 $data["tm_mon"] = self::$sugar_strptime_long_mon[$mon];
584 if ( isset($regexp['positions']['M']) && !empty($dateparts[$regexp['positions']['M']])) {
586 $mon = $dateparts[$regexp['positions']['M']];
587 if(isset(self::$sugar_strptime_short_mon[$mon])) {
588 $data["tm_mon"] = self::$sugar_strptime_short_mon[$mon];
593 if ( isset($regexp['positions']['a']) && !empty($dateparts[$regexp['positions']['a']])) {
594 $ampm = trim($dateparts[$regexp['positions']['a']]);
596 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
597 } else if($ampm == 'am') {
598 if($data["tm_hour"] == 12) {
600 $data["tm_hour"] = 0;
607 if ( isset($regexp['positions']['A']) && !empty($dateparts[$regexp['positions']['A']])) {
608 $ampm = trim($dateparts[$regexp['positions']['A']]);
610 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
611 } else if($ampm == 'AM') {
612 if($data["tm_hour"] == 12) {
614 $data["tm_hour"] = 0;
624 // 5.2 compatibility - 5.2 functions don't return $this, let's help them
628 * @see DateTime::setDate()
632 * @return SugarDateTime
634 public function setDate ($year, $month, $day)
636 parent::setDate($year, $month, $day);
642 * @see DateTime::setTime()
646 * @return SugarDateTime
648 public function setTime($hour, $minute, $sec = 0)
650 parent::setTime($hour, $minute, $sec);
656 * @see DateTime::modify()
658 * @return SugarDateTime
660 public function modify($modify)
662 if(PHP_VERSION_ID >= 50300 || $modify != 'first day of next month') {
663 parent::modify($modify);
665 /* PHP 5.2 does not understand 'first day of' and defaults need it */
666 $this->setDate($this->year, $this->month+1, 1);
673 * @see DateTime::setTimezone()
674 * @param DateTimeZone $timezone
675 * @return SugarDateTime
677 public function setTimezone ($timezone)
679 parent::setTimezone($timezone);