]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarDateTime.php
Release 6.4.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 /**
39  * Sugar DateTime container
40  * Extends regular PHP DateTime with useful services
41  * @api
42  */
43 class SugarDateTime extends DateTime
44 {
45     // Recognized properties and their formats
46         protected $formats = array(
47                 "sec" => "s",
48                 "min" => "i",
49                 "hour" => "G",
50                 "zhour" => "H",
51                 "day" => "j",
52                 "zday" => "d",
53                 "days_in_month" => "t",
54                 "day_of_week" => "w",
55                 "day_of_year" => "z",
56                 "week" => "W",
57                 "month" => "n",
58                 "zmonth" => "m",
59                 "year" => "Y",
60                 "am_pm" => "A",
61                 "hour_12" => "g",
62         );
63
64         // Property aliases
65         protected $var_gets = array(
66                 "24_hour" => "hour",
67                 "day_of_week" => "day_of_week_long",
68                 "day_of_week_short" => "day_of_week_short",
69                 "month_name" => "month_long",
70                 "hour" => "hour_12",
71         );
72
73         /**
74          * @var DateTimeZone
75          */
76         protected static $_gmt;
77
78     /**
79      * Calendar strings
80      * @var array
81      */
82     protected $_strings;
83
84     /**
85      * For testing - if we allowed to use PHP date parse
86      * @var bool
87      */
88     public static $use_php_parser = true;
89
90     /**
91      * For testing - if we allowed to use strptime()
92      * @var bool
93      */
94     public static $use_strptime = true;
95
96     /**
97          * Copy of DateTime::createFromFormat
98          *
99          * Needed to return right type of the object
100          *
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
106          */
107         public static function createFromFormat($format, $time, $timezone = null)
108         {
109             if(empty($time) || empty($format)) {
110                 return false;
111             }
112                 if(self::$use_php_parser && is_callable(array("DateTime", "createFromFormat"))) {
113                         // 5.3, hurray!
114                         if(!empty($timezone)) {
115                             $d = parent::createFromFormat($format, $time, $timezone);
116                         } else {
117                             $d = parent::createFromFormat($format, $time);
118                         }
119                 } else {
120                         // doh, 5.2, will have to simulate
121                         $d = self::_createFromFormat($format, $time, $timezone);
122                 }
123                 if(!$d) {
124                         return false;
125                 }
126                 $sd = new self($d->format(DateTime::ISO8601));
127                 $sd->setTimezone($d->getTimezone());
128                 return $sd;
129         }
130
131         /**
132          * Internal _createFromFormat implementation for 5.2
133      * @internal
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
139          */
140         protected static function _createFromFormat($format, $time, DateTimeZone $timezone = null)
141         {
142                 $res = new self();
143                 if(!empty($timezone)) {
144                     $res->setTimezone($timezone);
145                 }
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);
154                 if(empty($data)) {
155                         $GLOBALS['log']->error("Cannot parse $time for format $format");
156                     return null;
157                 }
158                 if($data["tm_year"] == 0) {
159                     unset($data["tm_year"]);
160                 }
161                 if($data["tm_mday"] == 0) {
162                     unset($data["tm_mday"]);
163                 }
164                 if(isset($data["tm_year"])) {
165                     $data["tm_year"] += 1900;
166                 }
167                 if(isset($data["tm_mon"])) {
168                     $data["tm_mon"]++;
169                 }
170                 $data += self::$data_init; // fill in missing parts
171                 } else {
172                     // Windows, etc. might not have strptime - we'd have to work harder here
173             $data = $res->_strptime($time, $format);
174                 }
175                 if(empty($data)) {
176                     $GLOBALS['log']->error("Cannot parse $time for format $format");
177                     return null;
178                 }
179                 if(isset($data["tm_year"])) {
180             $res->setDate($data["tm_year"], $data["tm_mon"], $data["tm_mday"]);
181                 }
182         $res->setTime($data["tm_hour"], $data["tm_min"], $data["tm_sec"]);
183                 return $res;
184         }
185
186         /**
187          * Load language Calendar strings
188      * @internal
189          * @param string $name string section to return
190          * @return array
191          */
192         protected function _getStrings($name)
193         {
194                 if(empty($this->_strings)) {
195                         $this->_strings = return_mod_list_strings_language($GLOBALS['current_language'],"Calendar");
196                 }
197                 return $this->_strings[$name];
198         }
199
200         /**
201          * Fetch property of the date by name
202          * @param string $var Property name
203          * @return mixed
204          */
205         public function __get($var)
206         {
207                 // simple formats
208                 if(isset($this->formats[$var])) {
209                         return $this->format($this->formats[$var]);
210                 }
211                 // conditional, derived and translated ones
212                 switch($var) {
213                         case "ts":
214                                 return $this->format("U")+0;
215                         case "tz_offset":
216                                 return $this->getTimezone()->getOffset($this);
217                         case "days_in_year":
218                                 return $this->format("L") == '1'?366:365;
219                                 break;
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];
226                         case "month_short":
227                                 $str = $this->_getStrings('dom_cal_month');
228                                 return $str[$this->month];
229                         case "month_long":
230                                 $str = $this->_getStrings('dom_cal_month_long');
231                                 return $str[$this->month];
232                 }
233
234                 return '';
235         }
236
237         /**
238          * Implement some get_ methods that fetch variables
239          *
240          * @param string $name
241          * @param array $args
242      * @return mixed
243      */
244         public function __call($name, $args)
245         {
246                 // fill in 5.2.x gaps
247                 if($name == "getTimestamp") {
248                         return $this->format('U')+0;
249                 }
250                 if($name == "setTimestamp") {
251                         $sec = (int)$args[0];
252                         $sd = new self("@$sec");
253                         $sd->setTimezone($this->getTimezone());
254                         return $sd;
255                 }
256
257                 // getters
258                 if(substr($name, 0, 4) == "get_") {
259                         $var = substr($name, 4);
260
261                         if(isset($this->var_gets[$var])) {
262                                 return $this->__get($this->var_gets[$var]);
263                         }
264
265                         if(isset($this->formats[$var])) {
266                                 return $this->__get($var);
267                         }
268                 }
269                 $GLOBALS['log']->fatal("SugarDateTime: unknowm method $name called");
270                 sugar_die("SugarDateTime: unknowm method $name called");
271                 return false;
272         }
273
274         /**
275          * Get specific hour of today
276          * @param int $hour_index
277          * @return SugarDateTime
278          */
279         public function get_datetime_by_index_today($hour_index)
280         {
281                 if ( $hour_index < 0 || $hour_index > 23  )
282                 {
283                         sugar_die("hour is outside of range");
284                 }
285
286                 $newdate = clone $this;
287                 $newdate->setTime($hour_index, 0, 0);
288                 return $newdate;
289         }
290
291         /**
292          * Get the last second of current hour
293          * @return SugarDateTime
294          */
295         function get_hour_end_time()
296         {
297                 $newdate = clone $this;
298                 $newdate->setTime($this->hour, 59, 59);
299                 return $newdate;
300         }
301
302         /**
303          * Get the last second of the current day
304          * @return SugarDateTime
305          */
306         function get_day_end_time()
307         {
308                 $newdate = clone $this;
309                 return $newdate->setTime(23, 59, 59);
310         }
311
312         /**
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
316          */
317         function get_day_by_index_this_week($day_index)
318         {
319                 $newdate = clone $this;
320                 $newdate->setDate($this->year, $this->month, $this->day +
321                         ($day_index - $this->day_of_week))->setTime(0,0);
322                 return $newdate;
323         }
324
325         /**
326          * Get the beginning of the last day of i's the month
327          * @deprecated
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
331          */
332         function get_day_by_index_this_year($month_index)
333         {
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);
338                 return $newdate;
339         }
340
341         /**
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
345          */
346         function get_day_by_index_this_month($day_index)
347         {
348                 $newdate = clone $this;
349                 return $newdate->setDate($this->year, $this->month, $day_index+1)->setTime(0, 0);
350         }
351
352         /**
353          * Get new date, modified by date expression
354          *
355          * @example $yesterday = $today->get("yesterday");
356          *
357          * @param string $expression
358          * @return SugarDateTime
359          */
360         function get($expression)
361         {
362                 $newdate = clone $this;
363                 $newdate->modify($expression);
364                 return $newdate;
365         }
366
367         /**
368          * Create from ISO 8601 datetime
369          * @param string $str
370          * @return SugarDateTime
371          */
372         static public function parse_utc_date_time($str)
373         {
374                 return new self($str);
375         }
376
377         /**
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
383      * @return array
384      */
385         static function getHashList($view, $start_time, $end_time)
386         {
387                 $hash_list = array();
388
389                 if ( $view != 'day')
390                 {
391                   $end_time = $end_time->get_day_end_time();
392                 }
393
394                 $end = $end_time->ts;
395                 if($end <= $start_time->ts) {
396                         $end = $start_time->ts+1;
397                 }
398
399                 $new_time = clone $start_time;
400                 $new_time->setTime($new_time->hour, 0, 0);
401
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");
406             } else {
407                 $hash_list[] = $new_time->format(TimeDate::DB_DATE_FORMAT);
408                 $new_time->modify("next day");
409             }
410         }
411
412                 return $hash_list;
413         }
414
415         /**
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
421      */
422         function get_day_begin($day = null, $month = null, $year = null)
423         {
424             $newdate = clone $this;
425             $newdate->setDate(
426                  $year?$year:$this->year,
427                  $month?$month:$this->month,
428                  $day?$day:$this->day);
429             $newdate->setTime(0, 0);
430             return $newdate;
431         }
432
433         /**
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
439          */
440         function get_day_end($day = null, $month = null, $year = null)
441         {
442             $newdate = clone $this;
443             $newdate->setDate(
444                  $year?$year:$this->year,
445                  $month?$month:$this->month,
446                  $day?$day:$this->day);
447             $newdate->setTime(23, 59, 59);
448             return $newdate;
449         }
450
451         /**
452          * Get the beginning of the first day of the year
453          * @param int $year
454          * @return SugarDateTime
455          */
456         function get_year_begin($year)
457         {
458         $newdate = clone $this;
459         $newdate->setDate($year, 1, 1);
460         $newdate->setTime(0,0);
461         return $newdate;
462         }
463
464         /**
465          * Print datetime in standard DB format
466          *
467          * Set $tz parameter to false if you are sure that the date is in UTC.
468          *
469          * @param bool $tz do conversion to UTC
470          * @return string
471          */
472         function asDb($tz = true)
473         {
474         if($tz) {
475             if(empty(self::$_gmt)) {
476                 self::$_gmt = new DateTimeZone("UTC");
477             }
478             $this->setTimezone(self::$_gmt);
479         }
480         return $this->format(TimeDate::DB_DATETIME_FORMAT);
481         }
482
483         /**
484          * Print date in standard DB format
485          *
486          * Set $tz parameter to false if you are sure that the date is in UTC.
487          *
488          * @param bool $tz do conversion to UTC
489          * @return string
490          */
491         function asDbDate($tz = true)
492         {
493         if($tz) {
494             if(empty(self::$_gmt)) {
495                 self::$_gmt = new DateTimeZone("UTC");
496             }
497             $this->setTimezone(self::$_gmt);
498         }
499         return $this->format(TimeDate::DB_DATE_FORMAT);
500         }
501
502         /**
503          * Get query string for the date, year=%d&month=%d&day=%d&hour=%d
504          * @return string
505          */
506         function get_date_str()
507         {
508         return sprintf("&year=%d&month=%d&day=%d&hour=%d", $this->year, $this->month, $this->day, $this->hour);
509         }
510
511         /**
512          * Convert date to string - 'r' format, like: Thu, 21 Dec 2000 16:01:07 +0200
513      * @return string
514      */
515         function __toString()
516         {
517             return $this->format('r');
518         }
519
520     /**
521      * Match between tm_ parts and date() format strings
522      * @var array
523      */
524         protected static $parts_match = array(
525             'Y' => 'tm_year',
526             'm' => 'tm_mon',
527             'n' => 'tm_mon',
528             'd' => 'tm_mday',
529             'H' => 'tm_hour',
530             'h' => 'tm_hour',
531             'i' => 'tm_min',
532             's' => 'tm_sec',
533     );
534
535     protected static $data_init = array(
536         "tm_hour" => 0,
537         "tm_min" => 0,
538         "tm_sec" => 0,
539     );
540
541     protected static $strptime_short_mon, $strptime_long_mon;
542         /**
543      * DateTime homebrew parser
544      *
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.
547      * @internal
548      * @param string $time  Time formatted string
549      * @param string $format Format, as accepted by strptime()
550      * @return array Parsed parts
551      */
552     protected function _strptime($time, $format)
553     {
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[""]);
558        }
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[""]);
562        }
563
564         $regexp = TimeDate::get_regular_expression($format);
565         if(!preg_match('@'.$regexp['format'].'@', $time, $dateparts)) {
566             return false;
567         }
568
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]];
572             }
573         }
574         // now process non-numeric ones
575         if ( isset($regexp['positions']['F']) && !empty($dateparts[$regexp['positions']['F']])) {
576                        // FIXME: locale?
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];
580             } else {
581                 return false;
582             }
583         }
584         if ( isset($regexp['positions']['M']) && !empty($dateparts[$regexp['positions']['M']])) {
585                        // FIXME: locale?
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];
589             } else {
590                 return false;
591             }
592         }
593         if ( isset($regexp['positions']['a']) && !empty($dateparts[$regexp['positions']['a']])) {
594             $ampm = trim($dateparts[$regexp['positions']['a']]);
595             if($ampm == 'pm') {
596                 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
597             } else if($ampm == 'am') {
598                 if($data["tm_hour"] == 12) {
599                     // 12:00am is 00:00
600                     $data["tm_hour"] = 0;
601                 }
602             } else {
603                 return false;
604             }
605         }
606
607         if ( isset($regexp['positions']['A']) && !empty($dateparts[$regexp['positions']['A']])) {
608             $ampm = trim($dateparts[$regexp['positions']['A']]);
609             if($ampm == 'PM') {
610                 if($data["tm_hour"] != 12) $data["tm_hour"] += 12;
611             } else if($ampm == 'AM') {
612                 if($data["tm_hour"] == 12) {
613                     // 12:00am is 00:00
614                     $data["tm_hour"] = 0;
615                 }
616             } else {
617                 return false;
618             }
619         }
620
621         return $data;
622     }
623
624     // 5.2 compatibility - 5.2 functions don't return $this, let's help them
625
626     /**
627      * (non-PHPdoc)
628      * @see DateTime::setDate()
629      * @param $year
630      * @param $month
631      * @param $day
632      * @return SugarDateTime
633      */
634     public function setDate ($year, $month, $day)
635     {
636         parent::setDate($year, $month, $day);
637         return $this;
638     }
639
640     /**
641      * (non-PHPdoc)
642      * @see DateTime::setTime()
643      * @param $hour
644      * @param $minute
645      * @param int $sec
646      * @return SugarDateTime
647      */
648     public function setTime($hour, $minute, $sec = 0)
649     {
650         parent::setTime($hour, $minute, $sec);
651         return $this;
652     }
653
654     /**
655      * (non-PHPdoc)
656      * @see DateTime::modify()
657      * @param $modify
658      * @return SugarDateTime
659      */
660     public function modify($modify)
661     {
662         if(PHP_VERSION_ID >= 50300 || $modify != 'first day of next month') {
663             parent::modify($modify);
664         } else {
665             /* PHP 5.2 does not understand 'first day of' and defaults need it */
666             $this->setDate($this->year, $this->month+1, 1);
667         }
668         return $this;
669     }
670
671     /**
672      * (non-PHPdoc)
673      * @see DateTime::setTimezone()
674      * @param DateTimeZone $timezone
675      * @return SugarDateTime
676      */
677     public function setTimezone ($timezone)
678     {
679         parent::setTimezone($timezone);
680         return $this;
681     }
682
683 }