]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarDateTime.php
Release 6.2.0
[Github/sugarcrm.git] / include / SugarDateTime.php
1 <?php
2 /*********************************************************************************
3  * SugarCRM Community Edition is a customer relationship management program developed by
4  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
5  * 
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.
12  * 
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
16  * details.
17  * 
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
21  * 02110-1301 USA.
22  * 
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.
25  * 
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.
29  * 
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  ********************************************************************************/
36
37
38 class SugarDateTime extends DateTime
39 {
40         protected $formats = array(
41                 "sec" => "s",
42                 "min" => "i",
43                 "hour" => "G",
44                 "zhour" => "H",
45                 "day" => "j",
46                 "zday" => "d",
47                 "days_in_month" => "t",
48                 "day_of_week" => "w",
49                 "day_of_year" => "z",
50                 "week" => "W",
51                 "month" => "n",
52                 "zmonth" => "m",
53                 "year" => "Y",
54                 "am_pm" => "A",
55                 "hour_12" => "g",
56         );
57
58         protected $var_gets = array(
59                 "24_hour" => "hour",
60                 "day_of_week" => "day_of_week_long",
61                 "day_of_week_short" => "day_of_week_short",
62                 "month_name" => "month_long",
63                 "hour" => "hour_12",
64         );
65
66         /**
67          * @var DateTimeZone
68          */
69         protected static $_gmt;
70
71     /**
72      * Calendar strings
73      * @var array
74      */
75     protected $_strings;
76
77     /**
78      * For testing - if we allowed to use PHP date parse
79      * @var bool
80      */
81     public static $use_php_parser = true;
82
83     /**
84      * For testing - if we allowed to use strptime()
85      * @var bool
86      */
87     public static $use_strptime = true;
88
89     /**
90          * Copy of DateTime::createFromFormat
91          *
92          * Needed to return right type of the object
93          *
94          * @param string $format
95          * @param string $time
96          * @param DateTimeZone $timezone
97          * @return SugarDateTime
98          * @see DateTime::createFromFormat
99          */
100         public static function createFromFormat($format, $time, DateTimeZone $timezone = null)
101         {
102             if(empty($time) || empty($format)) {
103                 return false;
104             }
105                 if(self::$use_php_parser && is_callable(array("DateTime", "createFromFormat"))) {
106                         // 5.3, hurray!
107                         if(!empty($timezone)) {
108                             $d = parent::createFromFormat($format, $time, $timezone);
109                         } else {
110                             $d = parent::createFromFormat($format, $time);
111                         }
112                 } else {
113                         // doh, 5.2, will have to simulate
114                         $d = self::_createFromFormat($format, $time, $timezone);
115                 }
116                 if(!$d) {
117                         return false;
118                 }
119                 $sd = new self("@".$d->getTimestamp());
120                 $sd->setTimezone($d->getTimezone());
121                 return $sd;
122         }
123
124         protected static function _createFromFormat($format, $time, DateTimeZone $timezone = null)
125         {
126                 $res = new self();
127                 if(!empty($timezone)) {
128                     $res->setTimezone($timezone);
129                 }
130                 if(self::$use_strptime && function_exists("strptime")) {
131                 $str_format = str_replace(array_keys(TimeDate::$format_to_str), array_values(TimeDate::$format_to_str), $format);
132                 // for a reason unknown to modern science, %P doesn't work in strptime
133                 $str_format = str_replace("%P", "%p", $str_format);
134                 // strip spaces before am/pm as our formats don't have them
135                 $time = preg_replace('/\s+(AM|PM)/i', '\1', $time);
136                 // TODO: better way to not risk locale stuff problems?
137                 $data = strptime($time, $str_format);
138                 if(empty($data)) {
139                         $GLOBALS['log']->error("Cannot parse $time for format $format");
140                     return null;
141                 }
142                 if($data["tm_year"] == 0) {
143                     unset($data["tm_year"]);
144                 }
145                 if($data["tm_mday"] == 0) {
146                     unset($data["tm_mday"]);
147                 }
148                 if(isset($data["tm_year"])) {
149                     $data["tm_year"] += 1900;
150                 }
151                 if(isset($data["tm_mon"])) {
152                     $data["tm_mon"]++;
153                 }
154                 $data += self::$data_init; // fill in missing parts
155                 } else {
156                     // Windows, etc. might not have strptime - we'd have to work harder here
157             $data = $res->_strptime($time, $format);
158                 }
159                 if(empty($data)) {
160                     $GLOBALS['log']->error("Cannot parse $time for format $format");
161                     return null;
162                 }
163                 if(isset($data["tm_year"])) {
164             $res->setDate($data["tm_year"], $data["tm_mon"], $data["tm_mday"]);
165                 }
166         $res->setTime($data["tm_hour"], $data["tm_min"], $data["tm_sec"]);
167                 return $res;
168         }
169
170         /**
171          * Load language strings
172          * @param string $name string section to return
173          * @return array
174          */
175         protected function _getStrings($name)
176         {
177                 if(empty($this->_strings)) {
178                         $this->_strings = return_mod_list_strings_language($GLOBALS['current_language'],"Calendar");
179                 }
180                 return $this->_strings[$name];
181         }
182
183         /**
184          * Fetch property of the date by name
185          * @param string $var Property name
186          */
187         public function __get($var)
188         {
189                 // simple formats
190                 if(isset($this->formats[$var])) {
191                         return $this->format($this->formats[$var]);
192                 }
193                 // conditional, derived and translated ones
194                 switch($var) {
195                         case "ts":
196                                 return $this->getTimestamp();
197                         case "tz_offset":
198                                 return $this->getTimezone()->getOffset($this);
199                         case "days_in_year":
200                                 return $this->format("L") == '1'?366:365;
201                                 break;
202                         case "day_of_week_short":
203                                 $str = $this->_getStrings('dom_cal_weekdays');
204                                 return $str[$this->day_of_week];
205                         case "day_of_week_long":
206                                 $str = $this->_getStrings('dom_cal_weekdays_long');
207                                 return $str[$this->day_of_week];
208                         case "month_short":
209                                 $str = $this->_getStrings('dom_cal_month');
210                                 return $str[$this->month];
211                         case "month_long":
212                                 $str = $this->_getStrings('dom_cal_month_long');
213                                 return $str[$this->month];
214                 }
215
216                 return '';
217         }
218
219         /**
220          * Implement some get_ methods that fetch variables
221          *
222          * @param string $name
223          * @param array $args
224          */
225         public function __call($name, $args)
226         {
227                 // fill in 5.2.x gaps
228                 if($name == "getTimestamp") {
229                         return (int)$this->format('U');
230                 }
231                 if($name == "setTimestamp") {
232                         $sec = (int)$args[0];
233                         $sd = new self("@$sec");
234                         $sd->setTimezone($this->getTimezone());
235                         return $sd;
236                 }
237
238                 // getters
239                 if(substr($name, 0, 4) == "get_") {
240                         $var = substr($name, 4);
241
242                         if(isset($this->var_gets[$var])) {
243                                 return $this->__get($this->var_gets[$var]);
244                         }
245
246                         if(isset($this->formats[$var])) {
247                                 return $this->__get($var);
248                         }
249                 }
250                 $GLOBALS['log']->fatal("SugarDateTime: unknowm method $name called");
251                 sugar_die("SugarDateTime: unknowm method $name called");
252                 return false;
253         }
254
255         /**
256          * Get specific hour of today
257          * @param int $hour_index
258          * @return SugarDateTime
259          */
260         public function get_datetime_by_index_today($hour_index)
261         {
262                 if ( $hour_index < 0 || $hour_index > 23  )
263                 {
264                         sugar_die("hour is outside of range");
265                 }
266
267                 $newdate = clone $this;
268                 $newdate->setTime($hour_index, 0, 0);
269                 return $newdate;
270         }
271
272         function get_hour_end_time()
273         {
274                 $newdate = clone $this;
275                 $newdate->setTime($this->hour, 59, 59);
276                 return $newdate;
277         }
278
279         function get_day_end_time()
280         {
281                 $newdate = clone $this;
282                 return $newdate->setTime(23, 59, 59);
283         }
284
285         function get_day_by_index_this_week($day_index)
286         {
287                 $newdate = clone $this;
288                 $newdate->setDate($this->year, $this->month, $this->day +
289                         ($day_index - $this->day_of_week))->setTime(0,0);
290                 return $newdate;
291         }
292
293         function get_day_by_index_this_year($month_index)
294         {
295                 $newdate = clone $this;
296                 $newdate->setDate($this->year, $month_index+1, 1);
297         $newdate->setDate($newdate->year, $newdate->month,  $newdate->days_in_month);
298                 $newdate->setTime(0, 0);
299                 return $newdate;
300         }
301
302         function get_day_by_index_this_month($day_index)
303         {
304                 $newdate = clone $this;
305                 return $newdate->setDate($this->year, $this->month, $day_index+1)->setTime(0, 0);
306         }
307
308         /**
309          * Get new date, modified by date expression
310          *
311          * @example $yesterday = $today->get("yesterday");
312          *
313          * @param string $expression
314          * @return SugarDateTime
315          */
316         function get($expression)
317         {
318                 $newdate = clone $this;
319                 $newdate->modify($expression);
320                 return $newdate;
321         }
322
323         /**
324          * Display as DB date
325          * @return string
326          */
327         function get_mysql_date()
328         {
329                 return $this->format(TimeDate::DB_DATE_FORMAT);
330         }
331
332         /**
333          * Create from ISO 8601 datetime
334          * @param string $str
335          * @return SugarDateTime
336          */
337         static public function parse_utc_date_time($str)
338         {
339                 return new self($str);
340         }
341
342         /**
343          * Create a list of time slots for calendar view
344          * Times must be in user TZ
345          * @param string $view Which view we are using - day, week, month
346          * @param SugarDateTime $start_time Start time
347          * @param SugarDateTime $end_time End time
348          */
349         static function getHashList($view, $start_time, $end_time)
350         {
351                 $hash_list = array();
352
353                 if ( $view != 'day')
354                 {
355                   $end_time = $end_time->get_day_end_time();
356                 }
357
358                 $end = $end_time->ts;
359                 if($end <= $start_time->ts) {
360                         $end = $start_time->ts+1;
361                 }
362
363                 $new_time = clone $start_time;
364                 $new_time->setTime($new_time->hour, 0, 0);
365
366         while ($new_time->ts < $end) {
367             if ($view == 'day') {
368                 $hash_list[] = $new_time->get_mysql_date() . ":" . $new_time->hour;
369                 $new_time->modify("next hour");
370             } else {
371                 $hash_list[] = $new_time->get_mysql_date();
372                 $new_time->modify("next day");
373             }
374         }
375
376                 return $hash_list;
377         }
378
379         /**
380          * Get the beginning of the given day
381          */
382         function get_day_begin($day = null, $month = null, $year = null)
383         {
384             $newdate = clone $this;
385             $newdate->setDate(
386                  $year?$year:$this->year,
387                  $month?$month:$this->month,
388                  $day?$day:$this->day);
389             $newdate->setTime(0, 0);
390             return $newdate;
391         }
392
393         /**
394          * Get the last timestamp of the given day
395          */
396         function get_day_end($day = null, $month = null, $year = null)
397         {
398             $newdate = clone $this;
399             $newdate->setDate(
400                  $year?$year:$this->year,
401                  $month?$month:$this->month,
402                  $day?$day:$this->day);
403             $newdate->setTime(23, 59, 59);
404             return $newdate;
405         }
406
407         function get_year_begin($year)
408         {
409         $newdate = clone $this;
410         $newdate->setDate($this->year, 1, 1);
411         $newdate->setTime(0,0);
412         return $newdate;
413         }
414         /*
415          * Print datetime in standard DB format
416          *
417          * Set $tz parameter to false if you are sure if the date is in UTC.
418          *
419          * @param bool $tz do conversion to UTC
420          * @return string
421          */
422         function asDb($tz = true)
423         {
424         if($tz) {
425             if(empty(self::$_gmt)) {
426                 self::$_gmt = new DateTimeZone("UTC");
427             }
428             $this->setTimezone(self::$_gmt);
429         }
430         return $this->format(TimeDate::DB_DATETIME_FORMAT);
431         }
432
433         /**
434          * Get query string for the date
435          * @return string
436          */
437         function get_date_str()
438         {
439         return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour);
440         }
441
442         function __toString()
443         {
444             return $this->format('r');
445         }
446
447     protected static $parts_match = array(
448             'Y' => 'tm_year',
449             'm' => 'tm_mon',
450             'n' => 'tm_mon',
451             'd' => 'tm_mday',
452             'H' => 'tm_hour',
453             'h' => 'tm_hour',
454             'i' => 'tm_min',
455             's' => 'tm_sec',
456     );
457
458     protected static $data_init = array(
459         "tm_hour" => 0,
460         "tm_min" => 0,
461         "tm_sec" => 0,
462     );
463
464     protected static $strptime_short_mon, $strptime_long_mon;
465         /**
466      * DateTime homebrew parser
467      *
468      * Since some OSes and PHP versions (please upgrade to 5.3!) do not support built-in parsing functions,
469      * we have to restort to this ugliness.
470      *
471      * @param string $format
472      * @param string $time
473      * @return array Parsed parts
474      */
475     protected function _strptime($time, $format)
476     {
477        $data = self::$data_init;
478        if(empty(self::$strptime_short_mon)) {
479            self::$strptime_short_mon = array_flip($this->_getStrings('dom_cal_month'));
480            unset(self::$strptime_short_mon[""]);
481        }
482        if(empty(self::$strptime_long_mon)) {
483            self::$strptime_long_mon = array_flip($this->_getStrings('dom_cal_month_long'));
484            unset(self::$strptime_long_mon[""]);
485        }
486
487         $regexp = TimeDate::get_regular_expression($format);
488         if(!preg_match('@'.$regexp['format'].'@', $time, $dateparts)) {
489             return false;
490         }
491
492         foreach(self::$parts_match as $part => $datapart) {
493             if (isset($regexp['positions'][$part]) && isset($dateparts[$regexp['positions'][$part]])) {
494                 $data[$datapart] = (int)$dateparts[$regexp['positions'][$part]];
495             }
496         }
497         // now process non-numeric ones
498         if ( isset($regexp['positions']['F']) && !empty($dateparts[$regexp['positions']['F']])) {
499                        // FIXME: locale?
500             $mon = $dateparts[$regexp['positions']['F']];
501             if(isset(self::$sugar_strptime_long_mon[$mon])) {
502                 $data["tm_mon"] = self::$sugar_strptime_long_mon[$mon];
503             } else {
504                 return false;
505             }
506         }
507         if ( isset($regexp['positions']['M']) && !empty($dateparts[$regexp['positions']['M']])) {
508                        // FIXME: locale?
509             $mon = $dateparts[$regexp['positions']['M']];
510             if(isset(self::$sugar_strptime_short_mon[$mon])) {
511                 $data["tm_mon"] = self::$sugar_strptime_short_mon[$mon];
512             } else {
513                 return false;
514             }
515         }
516         if ( isset($regexp['positions']['a']) && !empty($dateparts[$regexp['positions']['a']])) {
517             $ampm = trim($dateparts[$regexp['positions']['a']]);
518             if($ampm == 'pm') {
519                 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
520             } else if($ampm == 'am') {
521                 if($data["tm_hour"] == 12) {
522                     // 12:00am is 00:00
523                     $data["tm_hour"] = 0;
524                 }
525             } else {
526                 return false;
527             }
528         }
529
530         if ( isset($regexp['positions']['A']) && !empty($dateparts[$regexp['positions']['A']])) {
531             $ampm = trim($dateparts[$regexp['positions']['A']]);
532             if($ampm == 'PM') {
533                 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
534             } else if($ampm == 'AM') {
535                 if($data["tm_hour"] == 12) {
536                     // 12:00am is 00:00
537                     $data["tm_hour"] = 0;
538                 }
539             } else {
540                 return false;
541             }
542         }
543
544         return $data;
545     }
546
547     // 5.2 compatibility - 5.2 functions don't return $this, let's help them
548
549     /**
550      * (non-PHPdoc)
551      * @see DateTime::setDate()
552      * @return SugarDateTime
553      */
554     public function setDate ($year, $month, $day)
555     {
556         parent::setDate($year, $month, $day);
557         return $this;
558     }
559
560     /**
561      * (non-PHPdoc)
562      * @see DateTime::setTime()
563      * @return SugarDateTime
564      */
565     public function setTime($hour, $minute, $sec = 0)
566     {
567         parent::setTime($hour, $minute, $sec);
568         return $this;
569     }
570
571     /**
572      * (non-PHPdoc)
573      * @see DateTime::modify()
574      * @return SugarDateTime
575      */
576     public function modify($modify)
577     {
578         if(PHP_VERSION_ID >= 50300 || $modify != 'first day of next month') {
579             parent::modify($modify);
580         } else {
581             /* PHP 5.2 does not understand 'first day of' and defaults need it */
582             $this->setDate($this->year, $this->month+1, 1);
583         }
584         return $this;
585     }
586
587     /**
588      * (non-PHPdoc)
589      * @see DateTime::setTimezone()
590      * @return SugarDateTime
591      */
592     public function setTimezone (DateTimeZone $timezone)
593     {
594         parent::setTimezone($timezone);
595         return $this;
596     }
597
598 }