]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarDateTime.php
Release 6.2.0beta4
[Github/sugarcrm.git] / include / SugarDateTime.php
1 <?php
2 /*********************************************************************************
3  * SugarCRM 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          * Copy of DateTime::createFromFormat
79          *
80          * Needed to return right type of the object
81          *
82          * @param string $format
83          * @param string $time
84          * @param DateTimeZone $timezone
85          * @return SugarDateTime
86          * @see DateTime::createFromFormat
87          */
88         public static function createFromFormat($format, $time, DateTimeZone $timezone = null)
89         {
90             if(empty($time) || empty($format)) {
91                 return false;
92             }
93                 if(is_callable(array("DateTime", "createFromFormat"))) {
94                         // 5.3, hurray!
95                         if(!empty($timezone)) {
96                             $d = parent::createFromFormat($format, $time, $timezone);
97                         } else {
98                             $d = parent::createFromFormat($format, $time);
99                         }
100                 } else {
101                         // doh, 5.2, will have to simulate
102                         $d = self::_createFromFormat($format, $time, $timezone);
103                 }
104                 if(!$d) {
105                         return false;
106                 }
107                 $sd = new self("@".$d->getTimestamp());
108                 $sd->setTimezone($d->getTimezone());
109                 return $sd;
110         }
111
112         protected static function _createFromFormat($format, $time, DateTimeZone $timezone = null)
113         {
114                 $res = new self();
115                 if(!empty($timezone)) {
116                     $res->setTimezone($timezone);
117                 }
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);
124                 if(empty($data)) {
125                         $GLOBALS['log']->error("Cannot parse $time for format $format");
126                     return null;
127                 }
128                 if($data["tm_year"] == 0) {
129                     unset($data["tm_year"]);
130                 }
131                 if($data["tm_mday"] == 0) {
132                     unset($data["tm_mday"]);
133                 }
134                 if(isset($data["tm_year"])) {
135                     $data["tm_year"] += 1900;
136                 }
137                 if(isset($data["tm_mon"])) {
138                     $data["tm_mon"]++;
139                 }
140                 $data += self::$data_init; // fill in missing parts
141                 } else {
142                     // Windows, etc. might not have strptime - we'd have to work harder here
143             $data = $res->_strptime($time, $format);
144                 }
145                 if(empty($data)) {
146                     $GLOBALS['log']->error("Cannot parse $time for format $format");
147                     return null;
148                 }
149                 if(isset($data["tm_year"])) {
150             $res->setDate($data["tm_year"], $data["tm_mon"], $data["tm_mday"]);
151                 }
152         $res->setTime($data["tm_hour"], $data["tm_min"], $data["tm_sec"]);
153                 return $res;
154         }
155
156         /**
157          * Load language strings
158          * @param string $name string section to return
159          * @return array
160          */
161         protected function _getStrings($name)
162         {
163                 if(empty($this->_strings)) {
164                         $this->_strings = return_mod_list_strings_language($GLOBALS['current_language'],"Calendar");
165                 }
166                 return $this->_strings[$name];
167         }
168
169         /**
170          * Fetch property of the date by name
171          * @param string $var Property name
172          */
173         public function __get($var)
174         {
175                 // simple formats
176                 if(isset($this->formats[$var])) {
177                         return $this->format($this->formats[$var]);
178                 }
179                 // conditional, derived and translated ones
180                 switch($var) {
181                         case "ts":
182                                 return $this->getTimestamp();
183                         case "tz_offset":
184                                 return $this->getTimezone()->getOffset($this);
185                         case "days_in_year":
186                                 return $this->format("L") == '1'?366:365;
187                                 break;
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];
194                         case "month_short":
195                                 $str = $this->_getStrings('dom_cal_month');
196                                 return $str[$this->month];
197                         case "month_long":
198                                 $str = $this->_getStrings('dom_cal_month_long');
199                                 return $str[$this->month];
200                 }
201
202                 return '';
203         }
204
205         /**
206          * Implement some get_ methods that fetch variables
207          *
208          * @param string $name
209          * @param array $args
210          */
211         public function __call($name, $args)
212         {
213                 // fill in 5.2.x gaps
214                 if($name == "getTimestamp") {
215                         return (int)$this->format('U');
216                 }
217                 if($name == "setTimestamp") {
218                         $sec = (int)$args[0];
219                         $sd = new self("@$sec");
220                         $sd->setTimezone($this->getTimezone());
221                         return $sd;
222                 }
223
224                 // getters
225                 if(substr($name, 0, 4) == "get_") {
226                         $var = substr($name, 4);
227
228                         if(isset($this->var_gets[$var])) {
229                                 return $this->__get($this->var_gets[$var]);
230                         }
231
232                         if(isset($this->formats[$var])) {
233                                 return $this->__get($var);
234                         }
235                 }
236                 $GLOBALS['log']->fatal("SugarDateTime: unknowm method $name called");
237                 sugar_die("SugarDateTime: unknowm method $name called");
238                 return false;
239         }
240
241         /**
242          * Get specific hour of today
243          * @param int $hour_index
244          * @return SugarDateTime
245          */
246         public function get_datetime_by_index_today($hour_index)
247         {
248                 if ( $hour_index < 0 || $hour_index > 23  )
249                 {
250                         sugar_die("hour is outside of range");
251                 }
252
253                 $newdate = clone $this;
254                 $newdate->setTime($hour_index, 0, 0);
255                 return $newdate;
256         }
257
258         function get_hour_end_time()
259         {
260                 $newdate = clone $this;
261                 $newdate->setTime($this->hour, 59, 59);
262                 return $newdate;
263         }
264
265         function get_day_end_time()
266         {
267                 $newdate = clone $this;
268                 return $newdate->setTime(23, 59, 59);
269         }
270
271         function get_day_by_index_this_week($day_index)
272         {
273                 $newdate = clone $this;
274                 $newdate->setDate($this->year, $this->month, $this->day +
275                         ($day_index - $this->day_of_week))->setTime(0,0);
276                 return $newdate;
277         }
278
279         function get_day_by_index_this_year($month_index)
280         {
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);
285                 return $newdate;
286         }
287
288         function get_day_by_index_this_month($day_index)
289         {
290                 $newdate = clone $this;
291                 return $newdate->setDate($this->year, $this->month, $day_index+1)->setTime(0, 0);
292         }
293
294         /**
295          * Get new date, modified by date expression
296          *
297          * @example $yesterday = $today->get("yesterday");
298          *
299          * @param string $expression
300          * @return SugarDateTime
301          */
302         function get($expression)
303         {
304                 $newdate = clone $this;
305                 $newdate->modify($expression);
306                 return $newdate;
307         }
308
309         /**
310          * Display as DB date
311          * @return string
312          */
313         function get_mysql_date()
314         {
315                 return $this->format(TimeDate::DB_DATE_FORMAT);
316         }
317
318         /**
319          * Create from ISO 8601 datetime
320          * @param string $str
321          * @return SugarDateTime
322          */
323         static public function parse_utc_date_time($str)
324         {
325                 return new self($str);
326         }
327
328         /**
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
334          */
335         static function getHashList($view, $start_time, $end_time)
336         {
337                 $hash_list = array();
338
339                 if ( $view != 'day')
340                 {
341                   $end_time = $end_time->get_day_end_time();
342                 }
343
344                 $end = $end_time->ts;
345                 if($end <= $start_time->ts) {
346                         $end = $start_time->ts+1;
347                 }
348
349                 $new_time = clone $start_time;
350                 $new_time->setTime($new_time->hour, 0, 0);
351
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");
356             } else {
357                 $hash_list[] = $new_time->get_mysql_date();
358                 $new_time->modify("next day");
359             }
360         }
361
362                 return $hash_list;
363         }
364
365         /**
366          * Get the beginning of the given day
367          */
368         function get_day_begin($day = null, $month = null, $year = null)
369         {
370             $newdate = clone $this;
371             $newdate->setDate(
372                  $year?$year:$this->year,
373                  $month?$month:$this->month,
374                  $day?$day:$this->day);
375             $newdate->setTime(0, 0);
376             return $newdate;
377         }
378
379         /**
380          * Get the last timestamp of the given day
381          */
382         function get_day_end($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(23, 59, 59);
390             return $newdate;
391         }
392
393         function get_year_begin($year)
394         {
395         $newdate = clone $this;
396         $newdate->setDate($this->year, 1, 1);
397         $newdate->setTime(0,0);
398         return $newdate;
399         }
400         /*
401          * Print datetime in standard DB format
402          *
403          * Set $tz parameter to false if you are sure if the date is in UTC.
404          *
405          * @param bool $tz do conversion to UTC
406          * @return string
407          */
408         function asDb($tz = true)
409         {
410         if($tz) {
411             if(empty(self::$_gmt)) {
412                 self::$_gmt = new DateTimeZone("UTC");
413             }
414             $this->setTimezone(self::$_gmt);
415         }
416         return $this->format(TimeDate::DB_DATETIME_FORMAT);
417         }
418
419         /**
420          * Get query string for the date
421          * @return string
422          */
423         function get_date_str()
424         {
425         return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour);
426         }
427
428         function __toString()
429         {
430             return $this->format('r');
431         }
432
433     protected static $parts_match = array(
434             'Y' => 'tm_year',
435             'm' => 'tm_mon',
436             'n' => 'tm_mon',
437             'd' => 'tm_mday',
438             'H' => 'tm_hour',
439             'h' => 'tm_hour',
440             'i' => 'tm_min',
441             's' => 'tm_sec',
442     );
443
444     protected static $data_init = array(
445         "tm_hour" => 0,
446         "tm_min" => 0,
447         "tm_sec" => 0,
448     );
449
450     protected static $strptime_short_mon, $strptime_long_mon;
451         /**
452      * DateTime homebrew parser
453      *
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.
456      *
457      * @param string $format
458      * @param string $time
459      * @return array Parsed parts
460      */
461     protected function _strptime($time, $format)
462     {
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[""]);
467        }
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[""]);
471        }
472
473         $regexp = TimeDate::get_regular_expression($format);
474         if(!preg_match('@'.$regexp['format'].'@', $time, $dateparts)) {
475             return false;
476         }
477
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]];
481             }
482         }
483         // now process non-numeric ones
484         if ( isset($regexp['positions']['F']) && !empty($dateparts[$regexp['positions']['F']])) {
485                        // FIXME: locale?
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];
489             } else {
490                 return false;
491             }
492         }
493         if ( isset($regexp['positions']['M']) && !empty($dateparts[$regexp['positions']['M']])) {
494                        // FIXME: locale?
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];
498             } else {
499                 return false;
500             }
501         }
502         if ( isset($regexp['positions']['a']) && !empty($dateparts[$regexp['positions']['a']])) {
503             $ampm = $dateparts[$regexp['positions']['a']];
504             if($ampm == 'pm') {
505                 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
506             } else if($ampm == 'am') {
507                 if($data["tm_hour"] == 12) {
508                     // 12:00am is 00:00
509                     $data["tm_hour"] = 0;
510                 }
511             } else {
512                 return false;
513             }
514         }
515
516         if ( isset($regexp['positions']['A']) && !empty($dateparts[$regexp['positions']['A']])) {
517             $ampm = $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         return $data;
531     }
532
533     // 5.2 compatibility - 5.2 functions don't return $this, let's help them
534
535     /**
536      * (non-PHPdoc)
537      * @see DateTime::setDate()
538      * @return SugarDateTime
539      */
540     public function setDate ($year, $month, $day)
541     {
542         parent::setDate($year, $month, $day);
543         return $this;
544     }
545
546     /**
547      * (non-PHPdoc)
548      * @see DateTime::setTime()
549      * @return SugarDateTime
550      */
551     public function setTime($hour, $minute, $sec = 0)
552     {
553         parent::setTime($hour, $minute, $sec);
554         return $this;
555     }
556
557     /**
558      * (non-PHPdoc)
559      * @see DateTime::modify()
560      * @return SugarDateTime
561      */
562     public function modify($modify)
563     {
564         if(PHP_VERSION_ID >= 50300 || $modify != 'first day of next month') {
565             parent::modify($modify);
566         } else {
567             /* PHP 5.2 does not understand 'first day of' and defaults need it */
568             $this->setDate($this->year, $this->month+1, 1);
569         }
570         return $this;
571     }
572
573     /**
574      * (non-PHPdoc)
575      * @see DateTime::setTimezone()
576      * @return SugarDateTime
577      */
578     public function setTimezone (DateTimeZone $timezone)
579     {
580         parent::setTimezone($timezone);
581         return $this;
582     }
583
584 }