2 /*********************************************************************************
3 * SugarCRM 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 * Copy of DateTime::createFromFormat
80 * Needed to return right type of the object
82 * @param string $format
84 * @param DateTimeZone $timezone
85 * @return SugarDateTime
86 * @see DateTime::createFromFormat
88 public static function createFromFormat($format, $time, DateTimeZone $timezone = null)
90 if(empty($time) || empty($format)) {
93 if(is_callable(array("DateTime", "createFromFormat"))) {
95 if(!empty($timezone)) {
96 $d = parent::createFromFormat($format, $time, $timezone);
98 $d = parent::createFromFormat($format, $time);
101 // doh, 5.2, will have to simulate
102 $d = self::_createFromFormat($format, $time, $timezone);
107 $sd = new self("@".$d->getTimestamp());
108 $sd->setTimezone($d->getTimezone());
112 protected static function _createFromFormat($format, $time, DateTimeZone $timezone = null)
115 if(!empty($timezone)) {
116 $res->setTimezone($timezone);
118 if(function_exists("strptime")) {
119 $str_format = str_replace(array_keys(TimeDate::$format_to_str), array_values(TimeDate::$format_to_str), $format);
120 // for a reason unknown to modern science, %P doesn't work in strptime
121 $str_format = str_replace("%P", "%p", $str_format);
122 // TODO: better way to not risk locale stuff problems?
123 $data = strptime($time, $str_format);
125 $GLOBALS['log']->error("Cannot parse $time for format $format");
128 if($data["tm_year"] == 0) {
129 unset($data["tm_year"]);
131 if($data["tm_mday"] == 0) {
132 unset($data["tm_mday"]);
134 if(isset($data["tm_year"])) {
135 $data["tm_year"] += 1900;
137 if(isset($data["tm_mon"])) {
140 $data += self::$data_init; // fill in missing parts
142 // Windows, etc. might not have strptime - we'd have to work harder here
143 $data = $res->_strptime($time, $format);
146 $GLOBALS['log']->error("Cannot parse $time for format $format");
149 if(isset($data["tm_year"])) {
150 $res->setDate($data["tm_year"], $data["tm_mon"], $data["tm_mday"]);
152 $res->setTime($data["tm_hour"], $data["tm_min"], $data["tm_sec"]);
157 * Load language strings
158 * @param string $name string section to return
161 protected function _getStrings($name)
163 if(empty($this->_strings)) {
164 $this->_strings = return_mod_list_strings_language($GLOBALS['current_language'],"Calendar");
166 return $this->_strings[$name];
170 * Fetch property of the date by name
171 * @param string $var Property name
173 public function __get($var)
176 if(isset($this->formats[$var])) {
177 return $this->format($this->formats[$var]);
179 // conditional, derived and translated ones
182 return $this->getTimestamp();
184 return $this->getTimezone()->getOffset($this);
186 return $this->format("L") == '1'?366:365;
188 case "day_of_week_short":
189 $str = $this->_getStrings('dom_cal_weekdays');
190 return $str[$this->day_of_week];
191 case "day_of_week_long":
192 $str = $this->_getStrings('dom_cal_weekdays_long');
193 return $str[$this->day_of_week];
195 $str = $this->_getStrings('dom_cal_month');
196 return $str[$this->month];
198 $str = $this->_getStrings('dom_cal_month_long');
199 return $str[$this->month];
206 * Implement some get_ methods that fetch variables
208 * @param string $name
211 public function __call($name, $args)
213 // fill in 5.2.x gaps
214 if($name == "getTimestamp") {
215 return (int)$this->format('U');
217 if($name == "setTimestamp") {
218 $sec = (int)$args[0];
219 $sd = new self("@$sec");
220 $sd->setTimezone($this->getTimezone());
225 if(substr($name, 0, 4) == "get_") {
226 $var = substr($name, 4);
228 if(isset($this->var_gets[$var])) {
229 return $this->__get($this->var_gets[$var]);
232 if(isset($this->formats[$var])) {
233 return $this->__get($var);
236 $GLOBALS['log']->fatal("SugarDateTime: unknowm method $name called");
237 sugar_die("SugarDateTime: unknowm method $name called");
242 * Get specific hour of today
243 * @param int $hour_index
244 * @return SugarDateTime
246 public function get_datetime_by_index_today($hour_index)
248 if ( $hour_index < 0 || $hour_index > 23 )
250 sugar_die("hour is outside of range");
253 $newdate = clone $this;
254 $newdate->setTime($hour_index, 0, 0);
258 function get_hour_end_time()
260 $newdate = clone $this;
261 $newdate->setTime($this->hour, 59, 59);
265 function get_day_end_time()
267 $newdate = clone $this;
268 return $newdate->setTime(23, 59, 59);
271 function get_day_by_index_this_week($day_index)
273 $newdate = clone $this;
274 $newdate->setDate($this->year, $this->month, $this->day +
275 ($day_index - $this->day_of_week))->setTime(0,0);
279 function get_day_by_index_this_year($month_index)
281 $newdate = clone $this;
282 $newdate->setDate($this->year, $month_index+1, 1);
283 $newdate->setDate($newdate->year, $newdate->month, $newdate->days_in_month);
284 $newdate->setTime(0, 0);
288 function get_day_by_index_this_month($day_index)
290 $newdate = clone $this;
291 return $newdate->setDate($this->year, $this->month, $day_index+1)->setTime(0, 0);
295 * Get new date, modified by date expression
297 * @example $yesterday = $today->get("yesterday");
299 * @param string $expression
300 * @return SugarDateTime
302 function get($expression)
304 $newdate = clone $this;
305 $newdate->modify($expression);
313 function get_mysql_date()
315 return $this->format(TimeDate::DB_DATE_FORMAT);
319 * Create from ISO 8601 datetime
321 * @return SugarDateTime
323 static public function parse_utc_date_time($str)
325 return new self($str);
329 * Create a list of time slots for calendar view
330 * Times must be in user TZ
331 * @param string $view Which view we are using - day, week, month
332 * @param SugarDateTime $start_time Start time
333 * @param SugarDateTime $end_time End time
335 static function getHashList($view, $start_time, $end_time)
337 $hash_list = array();
341 $end_time = $end_time->get_day_end_time();
344 $end = $end_time->ts;
345 if($end <= $start_time->ts) {
346 $end = $start_time->ts+1;
349 $new_time = clone $start_time;
350 $new_time->setTime($new_time->hour, 0, 0);
352 while ($new_time->ts < $end) {
353 if ($view == 'day') {
354 $hash_list[] = $new_time->get_mysql_date() . ":" . $new_time->hour;
355 $new_time->modify("next hour");
357 $hash_list[] = $new_time->get_mysql_date();
358 $new_time->modify("next day");
366 * Get the beginning of the given day
368 function get_day_begin($day = null, $month = null, $year = null)
370 $newdate = clone $this;
372 $year?$year:$this->year,
373 $month?$month:$this->month,
374 $day?$day:$this->day);
375 $newdate->setTime(0, 0);
380 * Get the last timestamp of the given day
382 function get_day_end($day = null, $month = null, $year = null)
384 $newdate = clone $this;
386 $year?$year:$this->year,
387 $month?$month:$this->month,
388 $day?$day:$this->day);
389 $newdate->setTime(23, 59, 59);
393 function get_year_begin($year)
395 $newdate = clone $this;
396 $newdate->setDate($this->year, 1, 1);
397 $newdate->setTime(0,0);
401 * Print datetime in standard DB format
403 * Set $tz parameter to false if you are sure if the date is in UTC.
405 * @param bool $tz do conversion to UTC
408 function asDb($tz = true)
411 if(empty(self::$_gmt)) {
412 self::$_gmt = new DateTimeZone("UTC");
414 $this->setTimezone(self::$_gmt);
416 return $this->format(TimeDate::DB_DATETIME_FORMAT);
420 * Get query string for the date
423 function get_date_str()
425 return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour);
428 function __toString()
430 return $this->format('r');
433 protected static $parts_match = array(
444 protected static $data_init = array(
450 protected static $strptime_short_mon, $strptime_long_mon;
452 * DateTime homebrew parser
454 * Since some OSes and PHP versions (please upgrade to 5.3!) do not support built-in parsing functions,
455 * we have to restort to this ugliness.
457 * @param string $format
458 * @param string $time
459 * @return array Parsed parts
461 protected function _strptime($time, $format)
463 $data = self::$data_init;
464 if(empty(self::$strptime_short_mon)) {
465 self::$strptime_short_mon = array_flip($this->_getStrings('dom_cal_month'));
466 unset(self::$strptime_short_mon[""]);
468 if(empty(self::$strptime_long_mon)) {
469 self::$strptime_long_mon = array_flip($this->_getStrings('dom_cal_month_long'));
470 unset(self::$strptime_long_mon[""]);
473 $regexp = TimeDate::get_regular_expression($format);
474 if(!preg_match('@'.$regexp['format'].'@', $time, $dateparts)) {
478 foreach(self::$parts_match as $part => $datapart) {
479 if (isset($regexp['positions'][$part]) && isset($dateparts[$regexp['positions'][$part]])) {
480 $data[$datapart] = (int)$dateparts[$regexp['positions'][$part]];
483 // now process non-numeric ones
484 if ( isset($regexp['positions']['F']) && !empty($dateparts[$regexp['positions']['F']])) {
486 $mon = $dateparts[$regexp['positions']['F']];
487 if(isset(self::$sugar_strptime_long_mon[$mon])) {
488 $data["tm_mon"] = self::$sugar_strptime_long_mon[$mon];
493 if ( isset($regexp['positions']['M']) && !empty($dateparts[$regexp['positions']['M']])) {
495 $mon = $dateparts[$regexp['positions']['M']];
496 if(isset(self::$sugar_strptime_short_mon[$mon])) {
497 $data["tm_mon"] = self::$sugar_strptime_short_mon[$mon];
502 if ( isset($regexp['positions']['a']) && !empty($dateparts[$regexp['positions']['a']])) {
503 $ampm = $dateparts[$regexp['positions']['a']];
505 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
506 } else if($ampm == 'am') {
507 if($data["tm_hour"] == 12) {
509 $data["tm_hour"] = 0;
516 if ( isset($regexp['positions']['A']) && !empty($dateparts[$regexp['positions']['A']])) {
517 $ampm = $dateparts[$regexp['positions']['A']];
519 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
520 } else if($ampm == 'AM') {
521 if($data["tm_hour"] == 12) {
523 $data["tm_hour"] = 0;
533 // 5.2 compatibility - 5.2 functions don't return $this, let's help them
537 * @see DateTime::setDate()
538 * @return SugarDateTime
540 public function setDate ($year, $month, $day)
542 parent::setDate($year, $month, $day);
548 * @see DateTime::setTime()
549 * @return SugarDateTime
551 public function setTime($hour, $minute, $sec = 0)
553 parent::setTime($hour, $minute, $sec);
559 * @see DateTime::modify()
560 * @return SugarDateTime
562 public function modify($modify)
564 if(PHP_VERSION_ID >= 50300 || $modify != 'first day of next month') {
565 parent::modify($modify);
567 /* PHP 5.2 does not understand 'first day of' and defaults need it */
568 $this->setDate($this->year, $this->month+1, 1);
575 * @see DateTime::setTimezone()
576 * @return SugarDateTime
578 public function setTimezone (DateTimeZone $timezone)
580 parent::setTimezone($timezone);