]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/TimeDate.php
Release 6.5.0beta1
[Github/sugarcrm.git] / include / TimeDate.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 require_once 'include/SugarDateTime.php';
39
40 /**
41   * New Time & Date handling class
42   * @api
43   * Migration notes:
44   * - to_db_time() requires either full datetime or time, won't work with just date
45   *     The reason is that it's not possible to know if short string has only date or only time,
46   *     and it makes more sense to assume time for the time conversion function.
47   */
48 class TimeDate
49 {
50         const DB_DATE_FORMAT = 'Y-m-d';
51         const DB_TIME_FORMAT = 'H:i:s';
52     // little optimization
53         const DB_DATETIME_FORMAT = 'Y-m-d H:i:s';
54         const RFC2616_FORMAT = 'D, d M Y H:i:s \G\M\T';
55
56     const SECONDS_IN_A_DAY = 86400;
57
58     // Standard DB date/time formats
59     // they are constant, vars are for compatibility
60         public $dbDayFormat = self::DB_DATE_FORMAT;
61     public $dbTimeFormat = self::DB_TIME_FORMAT;
62
63     /**
64      * Regexp for matching format elements
65      * @var array
66      */
67     protected static $format_to_regexp = array(
68         'a' => '[ ]*[ap]m',
69         'A' => '[ ]*[AP]M',
70         'd' => '[0-9]{1,2}',
71         'j' => '[0-9]{1,2}',
72         'h' => '[0-9]{1,2}',
73         'H' => '[0-9]{1,2}',
74         'g' => '[0-9]{1,2}',
75         'G' => '[0-9]{1,2}',
76                 'i' => '[0-9]{1,2}',
77         'm' => '[0-9]{1,2}',
78         'n' => '[0-9]{1,2}',
79         'Y' => '[0-9]{4}',
80         's' => '[0-9]{1,2}',
81         'F' => '\w+',
82         "M" => '[\w]{1,3}',
83     );
84
85     /**
86      * Relation between date() and strftime() formats
87      * @var array
88      */
89     public static $format_to_str = array(
90                 // date
91         'Y' => '%Y',
92
93         'm' => '%m',
94         'M' => '%b',
95         'F' => '%B',
96             'n' => '%m',
97
98         'd' => '%d',
99         //'j' => '%e',
100         // time
101         'a' => '%P',
102         'A' => '%p',
103
104         'h' => '%I',
105         'H' => '%H',
106         //'g' => '%l',
107         //'G' => '%H',
108
109         'i' => '%M',
110         's' => '%S',
111     );
112
113     /**
114      * GMT timezone object
115      *
116      * @var DateTimeZone
117      */
118     protected static $gmtTimezone;
119
120     /**
121      * Current time
122      * @var SugarDateTime
123      */
124     protected $now;
125
126     /**
127      * The current user
128      *
129      * @var User
130      */
131     protected $user;
132
133     /**
134      * Current user's ID
135      *
136      * @var string
137      */
138     protected $current_user_id;
139     /**
140      * Current user's TZ
141      * @var DateTimeZone
142      */
143     protected $current_user_tz;
144
145     /**
146      * Separator for current user time format
147      *
148      * @var string
149      */
150     protected $time_separator;
151
152     /**
153      * Always consider user TZ to be GMT and date format DB format - for SOAP etc.
154      *
155      * @var bool
156      */
157     protected $always_db = false;
158
159     /**
160      * Global instance of TimeDate
161      * @var TimeDate
162      */
163     protected static $timedate;
164
165     /**
166      * Allow returning cached now() value
167      * If false, new system time is checked each time now() is required
168      * If true, same value is returned for whole request.
169      * Also, current user's timezone is cached.
170      * @var bool
171      */
172     public $allow_cache = true;
173
174     /**
175      * Create TimeDate handler
176      * @param User $user User to work with, default if current user
177      */
178     public function __construct(User $user = null)
179     {
180         if (self::$gmtTimezone == null) {
181             self::$gmtTimezone = new DateTimeZone("UTC");
182         }
183         $this->now = new SugarDateTime();
184         $this->tzGMT($this->now);
185         $this->user = $user;
186     }
187
188     /**
189      * Set flag specifying we should always use DB format
190      * @param bool $flag
191      * @return TimeDate
192      */
193     public function setAlwaysDb($flag = true)
194     {
195         $this->always_db = $flag;
196         $this->clearCache();
197         return $this;
198     }
199
200     /**
201      * Get "always use DB format" flag
202      * @return bool
203      */
204     public function isAlwaysDb()
205     {
206         return !empty($GLOBALS['disable_date_format']) || $this->always_db;
207     }
208
209     /**
210      * Get TimeDate instance
211      * @return TimeDate
212      */
213     public static function getInstance()
214     {
215         if(empty(self::$timedate)) {
216             if(ini_get('date.timezone') == '') {
217                 // Remove warning about default timezone
218                 date_default_timezone_set(@date('e'));
219                 try {
220                     $tz = self::guessTimezone();
221                 } catch(Exception $e) {
222                     $tz = "UTC"; // guess failed, switch to UTC
223                 }
224                 if(isset($GLOBALS['log'])) {
225                     $GLOBALS['log']->fatal("Configuration variable date.timezone is not set, guessed timezone $tz. Please set date.timezone=\"$tz\" in php.ini!");
226                 }
227                 date_default_timezone_set($tz);
228             }
229             self::$timedate = new self;
230         }
231         return self::$timedate;
232     }
233
234     /**
235      * Set current user for this object
236      *
237      * @param User $user User object, default is current user
238      * @return TimeDate
239      */
240     public function setUser(User $user = null)
241     {
242         $this->user = $user;
243         $this->clearCache();
244         return $this;
245     }
246
247     /**
248      * Figure out what the required user is
249      *
250      * The order is: supplied parameter, TimeDate's user, global current user
251      *
252      * @param User $user User object, default is current user
253      * @internal
254      * @return User
255      */
256     protected function _getUser(User $user = null)
257     {
258         if (empty($user)) {
259             $user = $this->user;
260         }
261         if (empty($user)) {
262             $user = $GLOBALS['current_user'];
263         }
264         return $user;
265     }
266
267     /**
268      * Get timezone for the specified user
269      *
270      * @param User $user User object, default is current user
271      * @return DateTimeZone
272      */
273     protected function _getUserTZ(User $user = null)
274     {
275         $user = $this->_getUser($user);
276         if (empty($user) || $this->isAlwaysDb()) {
277             return self::$gmtTimezone;
278         }
279
280         if ($this->allow_cache && $user->id == $this->current_user_id && ! empty($this->current_user_tz)) {
281             // current user is cached
282             return $this->current_user_tz;
283         }
284
285         $usertimezone = $user->getPreference('timezone');
286         if(empty($usertimezone)) {
287             return self::$gmtTimezone;
288         }
289         try {
290             $tz = new DateTimeZone($usertimezone);
291         } catch (Exception $e) {
292             $GLOBALS['log']->fatal('Unknown timezone: ' . $usertimezone);
293             return self::$gmtTimezone;
294         }
295
296         if (empty($this->current_user_id)) {
297             $this->current_user_id = $user->id;
298             $this->current_user_tz = $tz;
299         }
300
301         return $tz;
302     }
303
304     /**
305      * Clears all cached data regarding current user
306      */
307     public function clearCache()
308     {
309         $this->current_user_id = null;
310         $this->current_user_tz = null;
311         $this->time_separator = null;
312         $this->now = new SugarDateTime();
313     }
314
315     /**
316      * Get user date format.
317      * @todo add caching
318      *
319      * @param User $user user object, current user if not specified
320      * @return string
321      */
322     public function get_date_format(User $user = null)
323     {
324         $user = $this->_getUser($user);
325
326         if (empty($user) || $this->isAlwaysDb()) {
327             return self::DB_DATE_FORMAT;
328         }
329
330         $datef = $user->getPreference('datef');
331         if(empty($datef) && isset($GLOBALS['current_user']) && $GLOBALS['current_user'] !== $user) {
332             // if we got another user and it has no date format, try current user
333             $datef = $GLOBALS['current_user']->getPreference('datef');
334         }
335         if (empty($datef)) {
336             $datef = $GLOBALS['sugar_config']['default_date_format'];
337         }
338         if (empty($datef)) {
339             $datef = '';
340         }
341
342         return $datef;
343     }
344
345     /**
346      * Get user time format.
347      * @todo add caching
348      *
349      * @param User $user user object, current user if not specified
350      * @return string
351      */
352     public function get_time_format(/*User*/ $user = null)
353     {
354         if(is_bool($user) || func_num_args() > 1) {
355             // BC dance - old signature was boolean, User
356             $GLOBALS['log']->fatal('TimeDate::get_time_format(): Deprecated API used, please update you code - get_time_format() now has one argument of type User');
357             if(func_num_args() > 1) {
358                 $user = func_get_arg(1);
359             } else {
360                 $user = null;
361             }
362         }
363         $user = $this->_getUser($user);
364
365         if (empty($user) || $this->isAlwaysDb()) {
366             return self::DB_TIME_FORMAT;
367         }
368
369         $timef = $user->getPreference('timef');
370         if(empty($timef) && isset($GLOBALS['current_user']) && $GLOBALS['current_user'] !== $user) {
371             // if we got another user and it has no time format, try current user
372             $timef = $GLOBALS['current_user']->getPreference('$timef');
373         }
374         if (empty($timef)) {
375             $timef = $GLOBALS['sugar_config']['default_time_format'];
376         }
377         if (empty($timef)) {
378             $timef = '';
379         }
380         return $timef;
381     }
382
383     /**
384      * Get user datetime format.
385      *
386      * @param User $user user object, current user if not specified
387      * @return string
388      */
389     public function get_date_time_format($user = null)
390     {
391         // BC fix - had (bool, user) signature before
392         if(!($user instanceof User)) {
393             if(func_num_args() > 1) {
394                 $user = func_get_arg(1);
395                 if(!($user instanceof User)) {
396                     $user = null;
397                 }
398             } else {
399                 $user = null;
400             }
401         }
402
403         $cacheKey= $this->get_date_time_format_cache_key($user);
404         $cachedValue = sugar_cache_retrieve($cacheKey);
405
406         if(!empty($cachedValue) )
407         {
408             return $cachedValue;
409         }
410         else
411         {
412             $value = $this->merge_date_time($this->get_date_format($user), $this->get_time_format($user));
413             sugar_cache_put($cacheKey,$value,0);
414             return $value;
415         }
416     }
417
418     /**
419      * Retrieve the cache key used for user date/time formats
420      *
421      * @param $user
422      * @return string
423      */
424     public function get_date_time_format_cache_key($user)
425     {
426         $cacheKey = get_class($this) ."dateTimeFormat";
427
428         if($user instanceof User)
429         {
430            $cacheKey .= "_{$user->id}";
431         }
432
433         if( $this->isAlwaysDb() )
434             $cacheKey .= '_asdb';
435         
436         return $cacheKey;
437     }
438
439     /**
440      * Get user's first day of week setting.
441      *
442      * @param User $user user object, current user if not specified
443      * @return int Day, 0 = Sunday, 1 = Monday, etc...
444      */
445     public function get_first_day_of_week(User $user = null)
446     {
447         $user = $this->_getUser($user);
448         $fdow = 0;
449
450         if (!empty($user))
451         {
452           $fdow = $user->getPreference('fdow');
453           if (empty($fdow))
454               $fdow = 0;
455         }
456
457         return $fdow;
458     }
459
460
461     /**
462      * Make one datetime string from date string and time string
463      *
464      * @param string $date
465      * @param string $time
466      * @return string New datetime string
467      */
468     function merge_date_time($date, $time)
469     {
470         return $date . ' ' . $time;
471     }
472
473     /**
474      * Split datetime string into date & time
475      *
476      * @param string $datetime
477      * @return array
478      */
479     function split_date_time($datetime)
480     {
481         return explode(' ', $datetime, 2);
482     }
483
484
485     /**
486      * Get user date format in Javascript form
487      * @return string
488      */
489     function get_cal_date_format()
490     {
491         return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_format());
492     }
493
494     /**
495      * Get user time format in Javascript form
496      * @return string
497      */
498     function get_cal_time_format()
499     {
500         return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_time_format());
501     }
502
503     /**
504      * Get user date&time format in Javascript form
505      * @return string
506      */
507     function get_cal_date_time_format()
508     {
509         return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_time_format());
510     }
511
512     /**
513      * Verify if the date string conforms to a format
514      *
515      * @param string $date
516      * @param string $format Format to check
517      *
518      * @internal
519      * @return bool Is the date ok?
520      */
521     public function check_matching_format($date, $format)
522     {
523         try {
524             $dt = SugarDateTime::createFromFormat($format, $date);
525             if (!is_object($dt)) {
526                 return false;
527             }
528         } catch (Exception $e) {
529             return false;
530         }
531         return true;
532     }
533
534     /**
535      * Format DateTime object as DB datetime
536      *
537      * @param DateTime $date
538      * @return string
539      */
540     public function asDb(DateTime $date)
541     {
542         $date->setTimezone(self::$gmtTimezone);
543         return $date->format($this->get_db_date_time_format());
544     }
545
546     /**
547      * Format date as DB-formatted field type
548      * @param DateTime $date
549      * @param string $type Field type - date, time, datetime[combo]
550      * @return string Formatted date
551      */
552     public function asDbType(DateTime $date, $type)
553     {
554         switch($type) {
555             case "date":
556                 return $this->asDbDate($date);
557                 break;
558             case 'time':
559                 return $this->asDbtime($date);
560                 break;
561             case 'datetime':
562             case 'datetimecombo':
563             default:
564                 return $this->asDb($date);
565         }
566     }
567
568     /**
569      * Format DateTime object as user datetime
570      *
571      * @param DateTime $date
572      * @param User $user
573      * @return string
574      */
575     public function asUser(DateTime $date, User $user = null)
576     {
577         $this->tzUser($date, $user);
578         return $date->format($this->get_date_time_format($user));
579     }
580
581     /**
582      * Format date as user-formatted field type
583      * @param DateTime $date
584      * @param string $type Field type - date, time, datetime[combo]
585      * @param User $user
586      * @return string
587      */
588     public function asUserType(DateTime $date, $type, User $user = null)
589     {
590         switch($type) {
591             case "date":
592                 return $this->asUserDate($date, true, $user);
593                 break;
594             case 'time':
595                 return $this->asUserTime($date, true, $user);
596                 break;
597             case 'datetime':
598             case 'datetimecombo':
599             default:
600                 return $this->asUser($date, $user);
601         }
602     }
603
604     /**
605      * Produce timestamp offset by user's timezone
606      *
607      * So if somebody converts it to format assuming GMT, it would actually display user's time.
608      * This is used by Javascript.
609      *
610      * @param DateTime $date
611      * @param User $user
612      * @return int
613      */
614     public function asUserTs(DateTime $date, User $user = null)
615     {
616         return $date->format('U')+$this->_getUserTZ($user)->getOffset($date);
617     }
618
619     /**
620      * Format DateTime object as DB date
621      * Note: by default does not convert TZ!
622      * @param DateTime $date
623      * @param boolean $tz Perform TZ conversion?
624      * @return string
625      */
626     public function asDbDate(DateTime $date, $tz = false)
627     {
628         if($tz) $date->setTimezone(self::$gmtTimezone);
629         return $date->format($this->get_db_date_format());
630     }
631
632     /**
633      * Format DateTime object as user date
634      * Note: by default does not convert TZ!
635      * @param DateTime $date
636      * @param boolean $tz Perform TZ conversion?
637      * @param User $user
638      * @return string
639      */
640     public function asUserDate(DateTime $date, $tz = false, User $user = null)
641     {
642         if($tz) $this->tzUser($date, $user);
643         return $date->format($this->get_date_format($user));
644     }
645
646     /**
647      * Format DateTime object as DB time
648      *
649      * @param DateTime $date
650      * @return string
651      */
652     public function asDbTime(DateTime $date)
653     {
654         $date->setTimezone(self::$gmtTimezone);
655         return $date->format($this->get_db_time_format());
656     }
657
658     /**
659      * Format DateTime object as user time
660      *
661      * @param DateTime $date
662      * @param User $user
663      * @return string
664      */
665     public function asUserTime(DateTime $date, User $user = null)
666     {
667         $this->tzUser($date, $user);
668         return $date->format($this->get_time_format($user));
669     }
670
671     /**
672      * Get DateTime from DB datetime string
673      *
674      * @param string $date
675      * @return SugarDateTime
676      */
677     public function fromDb($date)
678     {
679         try {
680             return SugarDateTime::createFromFormat(self::DB_DATETIME_FORMAT, $date, self::$gmtTimezone);
681         } catch (Exception $e) {
682             $GLOBALS['log']->error("fromDb: Conversion of $date from DB format failed: {$e->getMessage()}");
683             return null;
684         }
685     }
686
687     /**
688      * Create a date from a certain type of field in DB format
689      * The types are: date, time, datatime[combo]
690      * @param string $date the datetime string
691      * @param string $type string type
692      * @return SugarDateTime
693      */
694     public function fromDbType($date, $type)
695     {
696         switch($type) {
697             case "date":
698                 return $this->fromDbDate($date);
699                 break;
700             case 'time':
701                 return $this->fromDbFormat($date, self::DB_TIME_FORMAT);
702                 break;
703             case 'datetime':
704             case 'datetimecombo':
705             default:
706                 return $this->fromDb($date);
707         }
708     }
709
710     /**
711      * Get DateTime from DB date string
712      *
713      * @param string $date
714      * @return SugarDateTime
715      */
716     public function fromDbDate($date)
717     {
718         try {
719             return SugarDateTime::createFromFormat(self::DB_DATE_FORMAT, $date, self::$gmtTimezone);
720         } catch (Exception $e) {
721             $GLOBALS['log']->error("fromDbDate: Conversion of $date from DB format failed: {$e->getMessage()}");
722             return null;
723         }
724     }
725
726     /**
727      * Get DateTime from DB datetime string using non-standard format
728      *
729      * Non-standard format usually would be only date, only time, etc.
730      *
731      * @param string $date
732      * @param string $format format to accept
733      * @return SugarDateTime
734      */
735     public function fromDbFormat($date, $format)
736     {
737         try {
738             return SugarDateTime::createFromFormat($format, $date, self::$gmtTimezone);
739         } catch (Exception $e) {
740             $GLOBALS['log']->error("fromDbFormat: Conversion of $date from DB format $format failed: {$e->getMessage()}");
741             return null;
742         }
743     }
744
745     /**
746      * Get DateTime from user datetime string
747      *
748      * @param string $date
749      * @param User $user
750      * @return SugarDateTime
751      */
752     public function fromUser($date, User $user = null)
753     {
754         $res = null;
755         try {
756             $res = SugarDateTime::createFromFormat($this->get_date_time_format($user), $date, $this->_getUserTZ($user));
757         } catch (Exception $e) {
758             $GLOBALS['log']->error("fromUser: Conversion of $date exception: {$e->getMessage()}");
759         }
760         if(!($res instanceof DateTime)) {
761             $uf = $this->get_date_time_format($user);
762             $GLOBALS['log']->error("fromUser: Conversion of $date from user format $uf failed");
763             return null;
764         }
765         return $res;
766     }
767
768     /**
769      * Create a date from a certain type of field in user format
770      * The types are: date, time, datatime[combo]
771      * @param string $date the datetime string
772      * @param string $type string type
773      * @param User $user
774      * @return SugarDateTime
775      */
776     public function fromUserType($date, $type, $user = null)
777     {
778         switch($type) {
779             case "date":
780                 return $this->fromUserDate($date, $user);
781                 break;
782             case 'time':
783                 return $this->fromUserTime($date, $user);
784                 break;
785             case 'datetime':
786             case 'datetimecombo':
787             default:
788                 return $this->fromUser($date, $user);
789         }
790     }
791
792     /**
793      * Get DateTime from user time string
794      *
795      * @param string $date
796      * @param User $user
797      * @return SugarDateTime
798      */
799     public function fromUserTime($date, User $user = null)
800     {
801         try {
802             return SugarDateTime::createFromFormat($this->get_time_format($user), $date, $this->_getUserTZ($user));
803         } catch (Exception $e) {
804             $uf = $this->get_time_format($user);
805             $GLOBALS['log']->error("fromUserTime: Conversion of $date from user format $uf failed: {$e->getMessage()}");
806             return null;
807         }
808     }
809
810     /**
811      * Get DateTime from user date string
812          * Usually for calendar-related functions like holidays
813      * Note: by default does not convert tz!
814      * @param string $date
815      * @param bool $convert_tz perform TZ converson?
816      * @param User $user
817      * @return SugarDateTime
818      */
819     public function fromUserDate($date, $convert_tz = false, User $user = null)
820     {
821         try {
822             return SugarDateTime::createFromFormat($this->get_date_format($user), $date, $convert_tz?$this->_getUserTZ($user):self::$gmtTimezone);
823         } catch (Exception $e) {
824             $uf = $this->get_date_format($user);
825             $GLOBALS['log']->error("fromUserDate: Conversion of $date from user format $uf failed: {$e->getMessage()}");
826             return null;
827         }
828     }
829
830     /**
831      * Create a date object from any string
832      *
833      * Same formats accepted as for DateTime ctor
834      *
835      * @param string $date
836      * @param User $user
837      * @return SugarDateTime
838      */
839     public function fromString($date, User $user = null)
840     {
841         try {
842             return new SugarDateTime($date, $this->_getUserTZ($user));
843         } catch (Exception $e) {
844             $GLOBALS['log']->error("fromString: Conversion of $date from string failed: {$e->getMessage()}");
845             return null;
846         }
847     }
848
849     /**
850      * Create DateTime from timestamp
851      *
852      * @param interger|string $ts
853      * @return SugarDateTime
854      */
855     public function fromTimestamp($ts)
856     {
857         return new SugarDateTime("@$ts");
858     }
859
860     /**
861      * Convert DateTime to GMT timezone
862      * @param DateTime $date
863      * @return DateTime
864      */
865     public function tzGMT(DateTime $date)
866     {
867         return $date->setTimezone(self::$gmtTimezone);
868     }
869
870     /**
871      * Convert DateTime to user timezone
872      * @param DateTime $date
873      * @param User $user
874      * @return DateTime
875      */
876     public function tzUser(DateTime $date, User $user = null)
877     {
878         return $date->setTimezone($this->_getUserTZ($user));
879     }
880
881     /**
882      * Get string defining midnight in current user's format
883      * @param string $format Time format to use
884      * @return string
885      */
886     protected function _get_midnight($format = null)
887     {
888         $zero = new DateTime("@0", self::$gmtTimezone);
889         return $zero->format($format?$format:$this->get_time_format());
890     }
891
892     /**
893      *
894      * Basic conversion function
895      *
896      * Converts between two string dates in different formats and timezones
897      *
898      * @param string $date
899      * @param string $fromFormat
900      * @param DateTimeZone $fromTZ
901      * @param string $toFormat
902      * @param DateTimeZone|null $toTZ
903      * @param bool $expand If string lacks time, expand it to include time
904      * @return string
905      */
906     protected function _convert($date, $fromFormat, $fromTZ, $toFormat, $toTZ, $expand = false)
907     {
908         $date = trim($date);
909         if (empty($date)) {
910             return $date;
911         }
912         try {
913             if ($expand && strlen($date) <= 10) {
914                 $date = $this->expandDate($date, $fromFormat);
915             }
916             $phpdate = SugarDateTime::createFromFormat($fromFormat, $date, $fromTZ);
917             if ($phpdate == false) {
918                 $GLOBALS['log']->error("convert: Conversion of $date from $fromFormat to $toFormat failed");
919                 return '';
920             }
921             if ($fromTZ !== $toTZ && $toTZ != null) {
922                 $phpdate->setTimeZone($toTZ);
923             }
924             return $phpdate->format($toFormat);
925         } catch (Exception $e) {
926             $GLOBALS['log']->error("Conversion of $date from $fromFormat to $toFormat failed: {$e->getMessage()}");
927             return '';
928         }
929     }
930
931     /**
932      * Convert DB datetime to local datetime
933      *
934      * TZ conversion is controlled by parameter
935      *
936      * @param string $date Original date in DB format
937      * @param bool $meridiem Ignored for BC
938      * @param bool $convert_tz Perform TZ conversion?
939      * @param User $user User owning the conversion formats
940      * @return string Date in display format
941      */
942     function to_display_date_time($date, $meridiem = true, $convert_tz = true, $user = null)
943     {
944         return $this->_convert($date, self::DB_DATETIME_FORMAT, self::$gmtTimezone, $this->get_date_time_format($user),
945             $convert_tz ? $this->_getUserTZ($user) : self::$gmtTimezone, true);
946     }
947
948     /**
949      * Converts DB time string to local time string
950      *
951      * TZ conversion depends on parameter
952      *
953      * @param string $date Time in DB format
954      * @param bool $meridiem
955      * @param bool $convert_tz Perform TZ conversion?
956      * @return string Time in user-defined format
957      */
958     public function to_display_time($date, $meridiem = true, $convert_tz = true)
959     {
960         if($convert_tz && strpos($date, ' ') === false) {
961             // we need TZ adjustment but have no date, assume today
962             $date = $this->expandTime($date, self::DB_DATETIME_FORMAT, self::$gmtTimezone);
963         }
964         return $this->_convert($date,
965             $convert_tz ? self::DB_DATETIME_FORMAT : self::DB_TIME_FORMAT, self::$gmtTimezone,
966             $this->get_time_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone);
967     }
968
969     /**
970      * Splits time in given format into components
971      *
972      * Components: h, m, s, a (am/pm) if format requires it
973      * If format has am/pm, hour is 12-based, otherwise 24-based
974      *
975      * @param string $date
976      * @param string $format
977      * @return array
978      */
979     public function splitTime($date, $format)
980     {
981         if (! ($date instanceof DateTime)) {
982             $date = SugarDateTime::createFromFormat($format, $date);
983         }
984         $ampm = strpbrk($format, 'aA');
985         $datearr = array(
986                 "h" => ($ampm == false) ? $date->format("H") : $date->format("h"),
987                 'm' => $date->format("i"),
988                 's' => $date->format("s")
989         );
990         if ($ampm) {
991             $datearr['a'] = ($ampm{0} == 'a') ? $date->format("a") : $date->format("A");
992         }
993         return $datearr;
994     }
995
996     /**
997      * Converts DB date string to local date string
998      *
999      * TZ conversion depens on parameter
1000      *
1001      * @param string $date Date in DB format
1002      * @param bool $convert_tz Perform TZ conversion?
1003      * @return string Date in user-defined format
1004      */
1005     public function to_display_date($date, $convert_tz = true)
1006     {
1007         return $this->_convert($date,
1008             self::DB_DATETIME_FORMAT, self::$gmtTimezone,
1009             $this->get_date_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone, true);
1010     }
1011
1012     /**
1013      * Convert date from format to format
1014      *
1015      * No TZ conversion is performed!
1016      *
1017      * @param string $date
1018      * @param string $from Source format
1019      * @param string $to Destination format
1020      * @return string Converted date
1021      */
1022     function to_display($date, $from, $to)
1023     {
1024         return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
1025     }
1026
1027     /**
1028      * Get DB datetime format
1029      * @return string
1030      */
1031     public function get_db_date_time_format()
1032     {
1033         return self::DB_DATETIME_FORMAT;
1034     }
1035
1036     /**
1037      * Get DB date format
1038      * @return string
1039      */
1040     public function get_db_date_format()
1041     {
1042         return self::DB_DATE_FORMAT;
1043     }
1044
1045     /**
1046      * Get DB time format
1047      * @return string
1048      */
1049     public function get_db_time_format()
1050     {
1051         return self::DB_TIME_FORMAT;
1052     }
1053
1054     /**
1055      * Convert date from local datetime to GMT-based DB datetime
1056      *
1057      * Includes TZ conversion.
1058      *
1059      * @param string $date
1060      * @return string Datetime in DB format
1061      */
1062     public function to_db($date)
1063     {
1064         return $this->_convert($date,
1065             $this->get_date_time_format(), $this->_getUserTZ(),
1066             $this->get_db_date_time_format(), self::$gmtTimezone,
1067             true);
1068     }
1069
1070     /**
1071      * Convert local datetime to DB date
1072      *
1073      * TZ conversion depends on parameter. If false, only format conversion is performed.
1074      *
1075      * @param string $date Local date
1076      * @param bool $convert_tz Should time and TZ be taken into account?
1077      * @return string Date in DB format
1078      */
1079     public function to_db_date($date, $convert_tz = true)
1080     {
1081         return $this->_convert($date,
1082             $this->get_date_time_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone,
1083             self::DB_DATE_FORMAT, self::$gmtTimezone, true);
1084     }
1085
1086     /**
1087      * Convert local datetime to DB time
1088      *
1089      * TZ conversion depends on parameter. If false, only format conversion is performed.
1090      *
1091      * @param string $date Local date
1092      * @param bool $convert_tz Should time and TZ be taken into account?
1093      * @return string Time in DB format
1094      */
1095     public function to_db_time($date, $convert_tz = true)
1096     {
1097         $format = $this->get_date_time_format();
1098         $tz = $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone;
1099         if($convert_tz && strpos($date, ' ') === false) {
1100             // we need TZ adjustment but have short string, expand it to full one
1101             // FIXME: if the string is short, should we assume date or time?
1102             $date = $this->expandTime($date, $format, $tz);
1103         }
1104         return $this->_convert($date,
1105             $convert_tz ? $format : $this->get_time_format(),
1106             $tz,
1107             self::DB_TIME_FORMAT, self::$gmtTimezone);
1108     }
1109
1110     /**
1111      * Takes a Date & Time value in local format and converts them to DB format
1112      * No TZ conversion!
1113      *
1114      * @param string $date
1115      * @param string $time
1116      * @return array Date & time in DB format
1117      **/
1118     public function to_db_date_time($date, $time)
1119     {
1120         try {
1121             $phpdate = SugarDateTime::createFromFormat($this->get_date_time_format(),
1122                 $this->merge_date_time($date, $time), self::$gmtTimezone);
1123             if ($phpdate == false) {
1124                 return array('', '');
1125             }
1126             return array($this->asDbDate($phpdate), $this->asDbTime($phpdate));
1127         } catch (Exception $e) {
1128             $GLOBALS['log']->error("Conversion of $date,$time failed");
1129             return array('', '');
1130         }
1131     }
1132
1133     /**
1134      * Return current time in DB format
1135      * @return string
1136      */
1137     public function nowDb()
1138     {
1139         if(!$this->allow_cache) {
1140             $nowGMT = $this->getNow();
1141         } else {
1142             $nowGMT = $this->now;
1143         }
1144         return $this->asDb($nowGMT);
1145     }
1146
1147     /**
1148      * Return current date in DB format
1149      * @return string
1150      */
1151     public function nowDbDate()
1152     {
1153         if(!$this->allow_cache) {
1154             $nowGMT = $this->getNow();
1155         } else {
1156             $nowGMT = $this->now;
1157         }
1158         return $this->asDbDate($nowGMT, true);
1159     }
1160
1161     /**
1162      * Get 'now' DateTime object
1163      * @param bool $userTz return in user timezone?
1164      * @return SugarDateTime
1165      */
1166     public function getNow($userTz = false)
1167     {
1168         if(!$this->allow_cache) {
1169             return new SugarDateTime("now", $userTz?$this->_getUserTz():self::$gmtTimezone);
1170         }
1171         // TODO: should we return clone?
1172         $now = clone $this->now;
1173         if($userTz) {
1174             return $this->tzUser($now);
1175         }
1176         return $now;
1177     }
1178
1179     /**
1180      * Set 'now' time
1181      * For testability - predictable time value
1182      * @param DateTime $now
1183      * @return TimeDate $this
1184      */
1185     public function setNow($now)
1186     {
1187         $this->now = $now;
1188         return $this;
1189     }
1190
1191     /**
1192      * Return current datetime in local format
1193      * @return string
1194      */
1195     public function now()
1196     {
1197         return  $this->asUser($this->getNow());
1198     }
1199
1200     /**
1201      * Return current date in User format
1202      * @return string
1203      */
1204     public function nowDate()
1205     {
1206         return  $this->asUserDate($this->getNow());
1207     }
1208
1209     /**
1210      * Get user format's time separator
1211      * @return string
1212      */
1213     public function timeSeparator()
1214     {
1215         if (empty($this->time_separator)) {
1216             $this->time_separator = $this->timeSeparatorFormat($this->get_time_format());
1217         }
1218         return $this->time_separator;
1219     }
1220
1221     /**
1222      * Find out format's time separator
1223      * @param string $timeformat Time format
1224      * @return stringS
1225      */
1226     public function timeSeparatorFormat($timeformat)
1227     {
1228         $date = $this->_convert("00:11:22", self::DB_TIME_FORMAT, null, $timeformat, null);
1229         if (preg_match('/\d+(.+?)11/', $date, $matches)) {
1230             $sep = $matches[1];
1231         } else {
1232             $sep = ':';
1233         }
1234         return $sep;
1235     }
1236
1237     /**
1238      * Returns start and end of a certain local date in GMT
1239      * Example: for May 19 in PDT start would be 2010-05-19 07:00:00, end would be 2010-05-20 06:59:59
1240      * @param string|DateTime $date Date in any suitable format
1241      * @param User $user
1242      * @return array Start & end date in start, startdate, starttime, end, enddate, endtime
1243      */
1244     public function getDayStartEndGMT($date, User $user = null)
1245     {
1246         if ($date instanceof DateTime) {
1247             $min = clone $date;
1248             $min->setTimezone($this->_getUserTZ($user));
1249             $max = clone $date;
1250             $max->setTimezone($this->_getUserTZ($user));
1251         } else {
1252             $min = new DateTime($date, $this->_getUserTZ($user));
1253             $max = new DateTime($date, $this->_getUserTZ($user));
1254         }
1255         $min->setTime(0, 0);
1256         $max->setTime(23, 59, 59);
1257
1258         $min->setTimezone(self::$gmtTimezone);
1259         $max->setTimezone(self::$gmtTimezone);
1260
1261         $result['start'] = $this->asDb($min);
1262         $result['startdate'] = $this->asDbDate($min);
1263         $result['starttime'] = $this->asDbTime($min);
1264         $result['end'] = $this->asDb($max);
1265         $result['enddate'] = $this->asDbDate($max);
1266         $result['endtime'] = $this->asDbtime($max);
1267
1268         return $result;
1269     }
1270
1271     /**
1272      * Expand date format by adding midnight to it
1273      * Note: date is assumed to be in target format already
1274      * @param string $date
1275      * @param string $format Target format
1276      * @return string
1277      */
1278     public function expandDate($date, $format)
1279     {
1280         $formats = $this->split_date_time($format);
1281         if(isset($formats[1])) {
1282             return $this->merge_date_time($date, $this->_get_midnight($formats[1]));
1283         }
1284         return $date;
1285     }
1286
1287     /**
1288      * Expand time format by adding today to it
1289      * Note: time is assumed to be in target format already
1290      * @param string $date
1291      * @param string $format Target format
1292      * @param DateTimeZone $tz
1293      * @return string
1294      */
1295     public function expandTime($date, $format, $tz)
1296     {
1297         $formats = $this->split_date_time($format);
1298         if(isset($formats[1])) {
1299             $now = clone $this->getNow();
1300             $now->setTimezone($tz);
1301             return $this->merge_date_time($now->format($formats[0]), $date);
1302         }
1303         return $date;
1304     }
1305
1306     /**
1307          * Get midnight (start of the day) in local time format
1308          *
1309          * @return Time string
1310          */
1311         function get_default_midnight()
1312         {
1313         return $this->_get_midnight($this->get_time_format());
1314         }
1315
1316         /**
1317          * Get the name of the timezone for the user
1318          * @param User $user User, default - current user
1319          * @return string
1320          */
1321         public static function userTimezone(User $user = null)
1322         {
1323             $user = self::getInstance()->_getUser($user);
1324             if(empty($user)) {
1325                 return '';
1326             }
1327             $tz = self::getInstance()->_getUserTZ($user);
1328             if($tz) {
1329                 return $tz->getName();
1330             }
1331             return '';
1332         }
1333
1334     /**
1335      * Guess the timezone for the current user
1336      * @param int $userOffset Offset from GMT in minutes
1337      * @return string
1338      */
1339         public static function guessTimezone($userOffset = 0)
1340         {
1341             if(!is_numeric($userOffset)) {
1342                     return '';
1343             }
1344             $defaultZones= array(
1345                 'America/Anchorage', 'America/Los_Angeles', 'America/Phoenix', 'America/Chicago',
1346                 'America/New_York', 'America/Argentina/Buenos_Aires', 'America/Montevideo',
1347                 'Europe/London', 'Europe/Amsterdam', 'Europe/Athens', 'Europe/Moscow',
1348                 'Asia/Tbilisi', 'Asia/Omsk', 'Asia/Jakarta', 'Asia/Hong_Kong',
1349                 'Asia/Tokyo', 'Pacific/Guam', 'Australia/Sydney', 'Australia/Perth',
1350             );
1351
1352             $now = new DateTime();
1353             $tzlist = timezone_identifiers_list();
1354             if($userOffset == 0) {
1355              $gmtOffset = date('Z');
1356                  $nowtz = date('e');
1357                  if(in_array($nowtz, $tzlist)) {
1358                  array_unshift($defaultZones, $nowtz);
1359                  } else {
1360                      $nowtz = timezone_name_from_abbr(date('T'), $gmtOffset, date('I'));
1361                      if(in_array($nowtz, $tzlist)) {
1362                          array_unshift($defaultZones, $nowtz);
1363                      }
1364                  }
1365         } else {
1366             $gmtOffset = $userOffset * 60;
1367         }
1368         foreach($defaultZones as $zoneName) {
1369                 $tz = new DateTimeZone($zoneName);
1370                 if($tz->getOffset($now) == $gmtOffset) {
1371                 return $tz->getName();
1372                 }
1373             }
1374         // try all zones
1375             foreach($tzlist as $zoneName) {
1376                 $tz = new DateTimeZone($zoneName);
1377                 if($tz->getOffset($now) == $gmtOffset) {
1378                 return $tz->getName();
1379                 }
1380             }
1381             return null;
1382         }
1383
1384     /**
1385      * Get the description of the user timezone for specific date
1386      * Like: PST(+08:00)
1387      * We need the date because it can be DST or non-DST
1388      * Note it's different from TZ name in tzName() that relates to current date
1389      * @param DateTime $date Current date
1390      * @param User $user User, default - current user
1391      * @return string
1392      */
1393         public static function userTimezoneSuffix(DateTime $date, User $user = null)
1394         {
1395             $user = self::getInstance()->_getUser($user);
1396             if(empty($user)) {
1397                 return '';
1398             }
1399             self::getInstance()->tzUser($date, $user);
1400             return $date->format('T(P)');
1401         }
1402
1403         /**
1404          * Get display name for a certain timezone
1405          * Note: it uses current date for GMT offset, so it may be not suitable for displaying generic dates
1406          * @param string|DateTimeZone $name TZ name
1407          * @return string
1408          */
1409         public static function tzName($name)
1410         {
1411             if(empty($name)) {
1412                 return '';
1413             }
1414             if($name instanceof DateTimeZone) {
1415                 $tz = $name;
1416             } else {
1417             $tz = timezone_open($name);
1418             }
1419         if(!$tz) {
1420             return "???";
1421         }
1422         $now = new DateTime("now", $tz);
1423         $off = $now->getOffset();
1424         $translated = translate('timezone_dom','',$name);
1425         if(is_string($translated) && !empty($translated)) {
1426             $name = $translated;
1427         }
1428         return sprintf("%s (GMT%+2d:%02d)%s", str_replace('_',' ', $name), $off/3600, (abs($off)/60)%60, "");//$now->format('I')==1?"(+DST)":"");
1429         }
1430
1431
1432     /**
1433      * Timezone sorting helper
1434      * Sorts by name
1435      * @param array $a
1436      * @param array $b
1437      * @internal
1438      * @return int
1439      */
1440         public static function _sortTz($a, $b)
1441         {
1442             if($a[0] == $b[0]) {
1443             return strcmp($a[1], $b[1]);
1444             } else {
1445                 return $a[0]<$b[0]?-1:1;
1446             }
1447         }
1448
1449         /**
1450          * Get list of all timezones in the system
1451          * @return array
1452          */
1453         public static function getTimezoneList()
1454         {
1455         $now = new DateTime();
1456         $res_zones = $zones = array();
1457             foreach(timezone_identifiers_list() as $zoneName) {
1458             $tz = new DateTimeZone($zoneName);
1459                 $zones[$zoneName] = array($tz->getOffset($now), self::tzName($zoneName));
1460             }
1461             uasort($zones, array('TimeDate', '_sortTz'));
1462             foreach($zones as $name => $zonedef) {
1463                 $res_zones[$name] = $zonedef[1];
1464             }
1465             return $res_zones;
1466         }
1467
1468     /**
1469      * Print timestamp in RFC2616 format:
1470      * @param int|null $ts Null means current ts
1471      * @return string
1472      */
1473         public static function httpTime($ts = null)
1474         {
1475             if($ts === null) {
1476                 $ts = time();
1477             }
1478             return gmdate(self::RFC2616_FORMAT, $ts);
1479         }
1480
1481         /**
1482          * Create datetime object from calendar array
1483          * @param array $time
1484          * @return SugarDateTime
1485          */
1486         public function fromTimeArray($time)
1487         {
1488                 if (! isset( $time) || count($time) == 0 )
1489                 {
1490                         return $this->nowDb();
1491                 }
1492                 elseif ( isset( $time['ts']))
1493                 {
1494                         return $this->fromTimestamp($time['ts']);
1495                 }
1496                 elseif ( isset( $time['date_str']))
1497                 {
1498                     return $this->fromDb($time['date_str']);
1499                 }
1500                 else
1501                 {
1502                 $hour = 0;
1503                 $min = 0;
1504                 $sec = 0;
1505                 $now = $this->getNow(true);
1506                 $day = $now->day;
1507                 $month = $now->month;
1508                 $year = $now->year;
1509                     if (isset($time['sec']))
1510                         {
1511                                 $sec = $time['sec'];
1512                         }
1513                         if (isset($time['min']))
1514                         {
1515                                 $min = $time['min'];
1516                         }
1517                         if (isset($time['hour']))
1518                         {
1519                                 $hour = $time['hour'];
1520                         }
1521                         if (isset($time['day']))
1522                         {
1523                                 $day = $time['day'];
1524                         }
1525                         if (isset($time['month']))
1526                         {
1527                                 $month = $time['month'];
1528                         }
1529                         if (isset($time['year']) && $time['year'] >= 1970)
1530                         {
1531                                 $year = $time['year'];
1532                         }
1533                         return $now->setDate($year, $month, $day)->setTime($hour, $min, $sec)->setTimeZone(self::$gmtTimezone);
1534                 }
1535         return null;
1536         }
1537
1538         /**
1539          * Returns the date portion of a datetime string
1540          *
1541          * @param string $datetime
1542          * @return string
1543          */
1544         public function getDatePart($datetime)
1545         {
1546             list($date, $time) = $this->split_date_time($datetime);
1547             return $date;
1548         }
1549
1550         /**
1551          * Returns the time portion of a datetime string
1552          *
1553          * @param string $datetime
1554          * @return string
1555          */
1556         public function getTimePart($datetime)
1557         {
1558             list($date, $time) = $this->split_date_time($datetime);
1559             return $time;
1560         }
1561
1562     /**
1563      * Returns the offset from user's timezone to GMT
1564      * @param User $user
1565      * @param DateTime $time When the offset is taken, default is now
1566      * @return int Offset in minutes
1567      */
1568     public function getUserUTCOffset(User $user = null, DateTime $time = null)
1569     {
1570         if(empty($time)) {
1571             $time = $this->now;
1572         }
1573         return $this->_getUserTZ($user)->getOffset($time) / 60;
1574     }
1575
1576     /**
1577      * Create regexp from datetime format
1578      * @param string $format
1579      * @return string Regular expression string
1580      */
1581     public static function get_regular_expression($format)
1582     {
1583         $newFormat = '';
1584         $regPositions = array();
1585         $ignoreNextChar = false;
1586         $count = 1;
1587         foreach (str_split($format) as $char) {
1588             if (! $ignoreNextChar && isset(self::$format_to_regexp[$char])) {
1589                 $newFormat .= '(' . self::$format_to_regexp[$char] . ')';
1590                 $regPositions[$char] = $count;
1591                 $count ++;
1592             } else {
1593                 $ignoreNextChar = false;
1594                 $newFormat .= $char;
1595
1596             }
1597             if ($char == "\\") {
1598                 $ignoreNextChar = true;
1599             }
1600         }
1601
1602         return array('format' => $newFormat, 'positions' => $regPositions);
1603     }
1604
1605     // format - date expression ('' means now) for start and end of the range
1606     protected $date_expressions = array(
1607         'yesterday' =>    array("-1 day", "-1 day"),
1608         'today' =>        array("", ""),
1609         'tomorrow' =>     array("+1 day", "+1 day"),
1610         'last_7_days' =>  array("-6 days", ""),
1611         'next_7_days' =>  array("", "+6 days"),
1612         'last_30_days' => array("-29 days", ""),
1613         'next_30_days' => array("", "+29 days"),
1614     );
1615
1616     /**
1617      * Parse date template
1618      * @internal
1619      * @param string $template Date expression
1620      * @param bool $daystart Do we want start or end of the day?
1621      * @param User $user
1622      * @param bool $adjustForTimezone
1623      * @return SugarDateTime
1624      */
1625     protected function parseFromTemplate($template, $daystart, User $user = null, $adjustForTimezone = true)
1626         {
1627         $rawTime = $this->getNow();
1628         $now = $adjustForTimezone?$this->tzUser($rawTime, $user):$rawTime;
1629         if(!empty($template)) {
1630             $now->modify($template);
1631         }
1632         if($daystart) {
1633             return $now->get_day_begin();
1634         } else {
1635             return $now->get_day_end();
1636         }
1637         }
1638
1639     /**
1640      * Get month-long range mdiff months from now
1641      * @internal
1642      * @param int $mdiff
1643      * @param User $user
1644      * @param bool $adjustForTimezone
1645      * @return array
1646      */
1647         protected function diffMon($mdiff, User $user = null, $adjustForTimezone = true)
1648         {
1649         $rawTime = $this->getNow();
1650         $now = $adjustForTimezone?$this->tzUser($rawTime, $user):$rawTime;
1651             $now->setDate($now->year, $now->month+$mdiff, 1);
1652             $start = $now->get_day_begin();
1653             $end = $now->setDate($now->year, $now->month, $now->days_in_month)->setTime(23, 59, 59);
1654             return array($start, $end);
1655         }
1656
1657     /**
1658      * Get year-long range ydiff years from now
1659      * @internal
1660      * @param int $ydiff
1661      * @param User $user
1662      * @param bool $adjustForTimezone
1663      * @return array
1664      */
1665         protected function diffYear($ydiff, User $user = null, $adjustForTimezone = true)
1666         {
1667         $rawTime = $this->getNow();
1668         $now = $adjustForTimezone?$this->tzUser($rawTime, $user):$rawTime;
1669         $now->setDate($now->year+$ydiff, 1, 1);
1670             $start = $now->get_day_begin();
1671             $end = $now->setDate($now->year, 12, 31)->setTime(23, 59, 59);
1672             return array($start, $end);
1673         }
1674
1675         /**
1676          * Parse date range expression
1677          * Returns beginning and end of the range as a date
1678          * @param string $range
1679          * @param User $user
1680      * @param bool $adjustForTimezone Do we need to adjust for timezone?
1681          * @return array of two Date objects, start & end
1682          */
1683         public function parseDateRange($range, User $user = null, $adjustForTimezone = true)
1684         {
1685         if(isset($this->date_expressions[$range])) {
1686             return array($this->parseFromTemplate($this->date_expressions[$range][0], true, $user, $adjustForTimezone),
1687                 $this->parseFromTemplate($this->date_expressions[$range][1], false, $user, $adjustForTimezone)
1688             );
1689         }
1690             switch($range) {
1691                         case 'next_month':
1692                             return $this->diffMon(1,  $user, $adjustForTimezone);
1693                     case 'last_month':
1694                             return $this->diffMon(-1,  $user, $adjustForTimezone);
1695                     case 'this_month':
1696                             return $this->diffMon(0,  $user, $adjustForTimezone);
1697                 case 'last_year':
1698                             return $this->diffYear(-1,  $user, $adjustForTimezone);
1699                 case 'this_year':
1700                             return $this->diffYear(0,  $user, $adjustForTimezone);
1701                 case 'next_year':
1702                             return $this->diffYear(1,  $user, $adjustForTimezone);
1703                 default:
1704                             return null;
1705             }
1706         }
1707
1708     /********************* OLD functions, should not be used publicly anymore ****************/
1709     /**
1710      * Merge time without am/pm with am/pm string
1711      * @TODO find better way to do this!
1712      * @deprecated for public use
1713      * @param string $date
1714      * @param string $format User time format
1715      * @param string $mer
1716      * @return string
1717      */
1718     function merge_time_meridiem($date, $format, $mer)
1719     {
1720         $date = trim($date);
1721         if (empty($date)) {
1722             return $date;
1723         }
1724         $fakeMerFormat = str_replace(array('a', 'A'), array('@~@', '@~@'), $format);
1725         $noMerFormat = str_replace(array('a', 'A'), array('', ''), $format);
1726         $newDate = $this->swap_formats($date, $noMerFormat, $fakeMerFormat);
1727         return str_replace('@~@', $mer, $newDate);
1728     }
1729
1730     /**
1731      * @deprecated for public use
1732      * Convert date from one format to another
1733      *
1734      * @param string $date
1735      * @param string $from
1736      * @param string $to
1737      * @return string
1738      */
1739     public function swap_formats($date, $from, $to)
1740     {
1741         return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
1742     }
1743
1744     /**
1745      * @deprecated for public use
1746      * handles offset values for Timezones and DST
1747      * @param   $date        string             date/time formatted in user's selected format
1748      * @param   $format      string             destination format value as passed to PHP's date() funtion
1749      * @param   $to                  boolean
1750      * @param   $user        object             user object from which Timezone and DST
1751      * @param   $usetimezone string             timezone name
1752      * values will be derived
1753      * @return   string         date formatted and adjusted for TZ and DST
1754      */
1755     function handle_offset($date, $format, $to = true, $user = null, $usetimezone = null)
1756     {
1757         $tz = empty($usetimezone)?$this->_getUserTZ($user):new DateTimeZone($usetimezone);
1758         $dateobj = new SugarDateTime($date, $to? self::$gmtTimezone : $tz);
1759         $dateobj->setTimezone($to ? $tz: self::$gmtTimezone);
1760         return $dateobj->format($format);
1761 //        return $this->_convert($date, $format, $to ? self::$gmtTimezone : $tz, $format, $to ? $tz : self::$gmtTimezone);
1762     }
1763
1764     /**
1765      * @deprecated for public use
1766      * Get current GMT datetime in DB format
1767      * @return string
1768      */
1769     function get_gmt_db_datetime()
1770     {
1771         return $this->nowDb();
1772     }
1773
1774     /**
1775      * @deprecated for public use
1776      * Get current GMT date in DB format
1777      * @return string
1778      */
1779     function get_gmt_db_date()
1780     {
1781         return $this->nowDbDate();
1782     }
1783
1784     /**
1785      * @deprecated for public use
1786      * this method will take an input $date variable (expecting Y-m-d format)
1787      * and get the GMT equivalent - with an hour-level granularity :
1788      * return the max value of a given locale's
1789      * date+time in GMT metrics (i.e., if in PDT, "2005-01-01 23:59:59" would be
1790      * "2005-01-02 06:59:59" in GMT metrics)
1791      * @param $date
1792      * @return array
1793      */
1794     function handleOffsetMax($date)
1795     {
1796         $min = new DateTime($date, $this->_getUserTZ());
1797         $min->setTime(0, 0);
1798         $max = new DateTime($date, $this->_getUserTZ());
1799         $max->setTime(23, 59, 59);
1800
1801         $min->setTimezone(self::$gmtTimezone);
1802         $max->setTimezone(self::$gmtTimezone);
1803
1804         $gmtDateTime['date'] = $this->asDbDate($max, false);
1805         $gmtDateTime['time'] = $this->asDbDate($max, false);
1806         $gmtDateTime['min'] = $this->asDb($min);
1807         $gmtDateTime['max'] = $this->asDb($max);
1808
1809         return $gmtDateTime;
1810     }
1811
1812     /**
1813      * @deprecated for public use
1814      * this returns the adjustment for a user against the server time
1815      *
1816      * @return integer number of minutes to adjust a time by to get the appropriate time for the user
1817      */
1818     public function adjustmentForUserTimeZone()
1819     {
1820         $tz = $this->_getUserTZ();
1821         $server_tz = new DateTimeZone(date_default_timezone_get());
1822         if ($tz && $server_tz) {
1823             return ($server_tz->getOffset($this->now) - $tz->getOffset($this->now)) / 60;
1824         }
1825         return 0;
1826     }
1827
1828     /**
1829      * @deprecated for public use
1830      * assumes that olddatetime is in Y-m-d H:i:s format
1831      * @param $olddatetime
1832      * @return string
1833      */
1834     function convert_to_gmt_datetime($olddatetime)
1835     {
1836         if (! empty($olddatetime)) {
1837             return date('Y-m-d H:i:s', strtotime($olddatetime) - date('Z'));
1838         }
1839         return '';
1840     }
1841
1842     /**
1843      * @deprecated for public use
1844      * get user timezone info
1845      * @param User $user
1846      * @return array
1847      */
1848     public function getUserTimeZone(User $user = null)
1849     {
1850         $tz = $this->_getUserTZ($user);
1851         return array("gmtOffset" => $tz->getOffset($this->now) / 60);
1852     }
1853
1854     /**
1855      * @deprecated for public use
1856      * get timezone start & end
1857      * @param $year
1858      * @param string $zone
1859      * @return array
1860      */
1861     public function getDSTRange($year, $zone = null)
1862     {
1863         if(!empty($zone)) {
1864                 $tz = timezone_open($zone);
1865         }
1866         if(empty($tz)) {
1867                 $tz = $this->_getUserTZ();
1868         }
1869
1870         $year_date = SugarDateTime::createFromFormat("Y", $year, self::$gmtTimezone);
1871         $year_end = clone $year_date;
1872         $year_end->setDate((int) $year, 12, 31);
1873         $year_end->setTime(23, 59, 59);
1874         $year_date->setDate((int) $year, 1, 1);
1875         $year_date->setTime(0, 0, 0);
1876                 $result = array();
1877         $transitions = $tz->getTransitions($year_date->ts, $year_end->ts);
1878         $idx = 0;
1879         if(version_compare(PHP_VERSION, '5.3.0', '<')) {
1880                 // <5.3.0 ignores parameters, advance manually to current year
1881                 $start_ts = $year_date->ts;
1882                 while(isset($transitions[$idx]) && $transitions[$idx]["ts"] < $start_ts) $idx++;
1883         }
1884         // get DST start
1885         while (isset($transitions[$idx]) && !$transitions[$idx]["isdst"]) $idx++;
1886         if(isset($transitions[$idx])) {
1887                 $result["start"] = $this->fromTimestamp($transitions[$idx]["ts"])->asDb();
1888         }
1889         // get DST end
1890         while (isset($transitions[$idx]) && $transitions[$idx]["isdst"]) $idx++;
1891         if(isset($transitions[$idx])) {
1892                 $result["end"] = $this->fromTimestamp($transitions[$idx]["ts"])->asDb();
1893         }
1894         return $result;
1895     }
1896
1897 /****************** GUI stuff that really shouldn't be here, will be moved ************/
1898     /**
1899      * Get Javascript variables setup for user date format validation
1900      * @deprecated moved to SugarView
1901      * @return string JS code
1902      */
1903     function get_javascript_validation()
1904     {
1905         return SugarView::getJavascriptValidation();
1906     }
1907
1908     /**
1909      * AMPMMenu
1910      * This method renders a <select> HTML form element based on the
1911      * user's time format preferences, with give date's value highlighted.
1912      *
1913      * If user's prefs have no AM/PM string, returns empty string.
1914      *
1915      * @todo There is hardcoded HTML in here that does not allow for localization
1916      * of the AM/PM am/pm Strings in this drop down menu.  Also, perhaps
1917      * change to the substr_count function calls to strpos
1918      * TODO: Remove after full switch to fields
1919      * @deprecated
1920      * @param string $prefix Prefix for SELECT
1921      * @param string $date Date in display format
1922      * @param string $attrs Additional attributes for SELECT
1923      * @return string SELECT HTML
1924      */
1925     function AMPMMenu($prefix, $date, $attrs = '')
1926     {
1927         $tf = $this->get_time_format();
1928         $am = strpbrk($tf, 'aA');
1929         if ($am == false) {
1930             return '';
1931         }
1932         $selected = array("am" => "", "pm" => "", "AM" => "", "PM" => "");
1933         if (preg_match('/([ap]m)/i', $date, $match)) {
1934             $selected[$match[1]] = " selected";
1935         }
1936
1937         $menu = "<select name='" . $prefix . "meridiem' " . $attrs . ">";
1938         if ($am{0} == 'a') {
1939             $menu .= "<option value='am'{$selected["am"]}>am";
1940             $menu .= "<option value='pm'{$selected["pm"]}>pm";
1941         } else {
1942             $menu .= "<option value='AM'{$selected["AM"]}>AM";
1943             $menu .= "<option value='PM'{$selected["PM"]}>PM";
1944         }
1945
1946         return $menu . '</select>';
1947     }
1948
1949     /**
1950      * Get user format in JS form
1951      * TODO: Remove after full switch to fields
1952      * @return string
1953      */
1954     function get_user_date_format()
1955     {
1956         return str_replace(array('Y', 'm', 'd'), array('yyyy', 'mm', 'dd'), $this->get_date_format());
1957     }
1958
1959     /**
1960      * Get user time format example
1961      * TODO: Remove after full switch to fields
1962      * @deprecated
1963      * @return string
1964      */
1965     function get_user_time_format()
1966     {
1967         global $sugar_config;
1968         $time_pref = $this->get_time_format();
1969
1970         if (! empty($time_pref) && ! empty($sugar_config['time_formats'][$time_pref])) {
1971             return $sugar_config['time_formats'][$time_pref];
1972         }
1973
1974         return '23:00'; //default
1975     }
1976
1977 }