]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/TimeDate.php
Release 6.5.0
[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         $user = $this->_getUser($user);
428
429         if($user instanceof User)
430         {
431            $cacheKey .= "_{$user->id}";
432         }
433
434         if( $this->isAlwaysDb() )
435             $cacheKey .= '_asdb';
436         
437         return $cacheKey;
438     }
439
440     /**
441      * Get user's first day of week setting.
442      *
443      * @param User $user user object, current user if not specified
444      * @return int Day, 0 = Sunday, 1 = Monday, etc...
445      */
446     public function get_first_day_of_week(User $user = null)
447     {
448         $user = $this->_getUser($user);
449         $fdow = 0;
450
451         if (!empty($user))
452         {
453           $fdow = $user->getPreference('fdow');
454           if (empty($fdow))
455               $fdow = 0;
456         }
457
458         return $fdow;
459     }
460
461
462     /**
463      * Make one datetime string from date string and time string
464      *
465      * @param string $date
466      * @param string $time
467      * @return string New datetime string
468      */
469     function merge_date_time($date, $time)
470     {
471         return $date . ' ' . $time;
472     }
473
474     /**
475      * Split datetime string into date & time
476      *
477      * @param string $datetime
478      * @return array
479      */
480     function split_date_time($datetime)
481     {
482         return explode(' ', $datetime, 2);
483     }
484
485
486     /**
487      * Get user date format in Javascript form
488      * @return string
489      */
490     function get_cal_date_format()
491     {
492         return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_format());
493     }
494
495     /**
496      * Get user time format in Javascript form
497      * @return string
498      */
499     function get_cal_time_format()
500     {
501         return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_time_format());
502     }
503
504     /**
505      * Get user date&time format in Javascript form
506      * @return string
507      */
508     function get_cal_date_time_format()
509     {
510         return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_time_format());
511     }
512
513     /**
514      * Verify if the date string conforms to a format
515      *
516      * @param string $date
517      * @param string $format Format to check
518      *
519      * @internal
520      * @return bool Is the date ok?
521      */
522     public function check_matching_format($date, $format)
523     {
524         try {
525             $dt = SugarDateTime::createFromFormat($format, $date);
526             if (!is_object($dt)) {
527                 return false;
528             }
529         } catch (Exception $e) {
530             return false;
531         }
532         return true;
533     }
534
535     /**
536      * Format DateTime object as DB datetime
537      *
538      * @param DateTime $date
539      * @return string
540      */
541     public function asDb(DateTime $date)
542     {
543         $date->setTimezone(self::$gmtTimezone);
544         return $date->format($this->get_db_date_time_format());
545     }
546
547     /**
548      * Format date as DB-formatted field type
549      * @param DateTime $date
550      * @param string $type Field type - date, time, datetime[combo]
551      * @return string Formatted date
552      */
553     public function asDbType(DateTime $date, $type)
554     {
555         switch($type) {
556             case "date":
557                 return $this->asDbDate($date);
558                 break;
559             case 'time':
560                 return $this->asDbtime($date);
561                 break;
562             case 'datetime':
563             case 'datetimecombo':
564             default:
565                 return $this->asDb($date);
566         }
567     }
568
569     /**
570      * Format DateTime object as user datetime
571      *
572      * @param DateTime $date
573      * @param User $user
574      * @return string
575      */
576     public function asUser(DateTime $date, User $user = null)
577     {
578         $this->tzUser($date, $user);
579         return $date->format($this->get_date_time_format($user));
580     }
581
582     /**
583      * Format date as user-formatted field type
584      * @param DateTime $date
585      * @param string $type Field type - date, time, datetime[combo]
586      * @param User $user
587      * @return string
588      */
589     public function asUserType(DateTime $date, $type, User $user = null)
590     {
591         switch($type) {
592             case "date":
593                 return $this->asUserDate($date, true, $user);
594                 break;
595             case 'time':
596                 return $this->asUserTime($date, true, $user);
597                 break;
598             case 'datetime':
599             case 'datetimecombo':
600             default:
601                 return $this->asUser($date, $user);
602         }
603     }
604
605     /**
606      * Produce timestamp offset by user's timezone
607      *
608      * So if somebody converts it to format assuming GMT, it would actually display user's time.
609      * This is used by Javascript.
610      *
611      * @param DateTime $date
612      * @param User $user
613      * @return int
614      */
615     public function asUserTs(DateTime $date, User $user = null)
616     {
617         return $date->format('U')+$this->_getUserTZ($user)->getOffset($date);
618     }
619
620     /**
621      * Format DateTime object as DB date
622      * Note: by default does not convert TZ!
623      * @param DateTime $date
624      * @param boolean $tz Perform TZ conversion?
625      * @return string
626      */
627     public function asDbDate(DateTime $date, $tz = false)
628     {
629         if($tz) $date->setTimezone(self::$gmtTimezone);
630         return $date->format($this->get_db_date_format());
631     }
632
633     /**
634      * Format DateTime object as user date
635      * Note: by default does not convert TZ!
636      * @param DateTime $date
637      * @param boolean $tz Perform TZ conversion?
638      * @param User $user
639      * @return string
640      */
641     public function asUserDate(DateTime $date, $tz = false, User $user = null)
642     {
643         if($tz) $this->tzUser($date, $user);
644         return $date->format($this->get_date_format($user));
645     }
646
647     /**
648      * Format DateTime object as DB time
649      *
650      * @param DateTime $date
651      * @return string
652      */
653     public function asDbTime(DateTime $date)
654     {
655         $date->setTimezone(self::$gmtTimezone);
656         return $date->format($this->get_db_time_format());
657     }
658
659     /**
660      * Format DateTime object as user time
661      *
662      * @param DateTime $date
663      * @param User $user
664      * @return string
665      */
666     public function asUserTime(DateTime $date, User $user = null)
667     {
668         $this->tzUser($date, $user);
669         return $date->format($this->get_time_format($user));
670     }
671
672     /**
673      * Get DateTime from DB datetime string
674      *
675      * @param string $date
676      * @return SugarDateTime
677      */
678     public function fromDb($date)
679     {
680         try {
681             return SugarDateTime::createFromFormat(self::DB_DATETIME_FORMAT, $date, self::$gmtTimezone);
682         } catch (Exception $e) {
683             $GLOBALS['log']->error("fromDb: Conversion of $date from DB format failed: {$e->getMessage()}");
684             return null;
685         }
686     }
687
688     /**
689      * Create a date from a certain type of field in DB format
690      * The types are: date, time, datatime[combo]
691      * @param string $date the datetime string
692      * @param string $type string type
693      * @return SugarDateTime
694      */
695     public function fromDbType($date, $type)
696     {
697         switch($type) {
698             case "date":
699                 return $this->fromDbDate($date);
700                 break;
701             case 'time':
702                 return $this->fromDbFormat($date, self::DB_TIME_FORMAT);
703                 break;
704             case 'datetime':
705             case 'datetimecombo':
706             default:
707                 return $this->fromDb($date);
708         }
709     }
710
711     /**
712      * Get DateTime from DB date string
713      *
714      * @param string $date
715      * @return SugarDateTime
716      */
717     public function fromDbDate($date)
718     {
719         try {
720             return SugarDateTime::createFromFormat(self::DB_DATE_FORMAT, $date, self::$gmtTimezone);
721         } catch (Exception $e) {
722             $GLOBALS['log']->error("fromDbDate: Conversion of $date from DB format failed: {$e->getMessage()}");
723             return null;
724         }
725     }
726
727     /**
728      * Get DateTime from DB datetime string using non-standard format
729      *
730      * Non-standard format usually would be only date, only time, etc.
731      *
732      * @param string $date
733      * @param string $format format to accept
734      * @return SugarDateTime
735      */
736     public function fromDbFormat($date, $format)
737     {
738         try {
739             return SugarDateTime::createFromFormat($format, $date, self::$gmtTimezone);
740         } catch (Exception $e) {
741             $GLOBALS['log']->error("fromDbFormat: Conversion of $date from DB format $format failed: {$e->getMessage()}");
742             return null;
743         }
744     }
745
746     /**
747      * Get DateTime from user datetime string
748      *
749      * @param string $date
750      * @param User $user
751      * @return SugarDateTime
752      */
753     public function fromUser($date, User $user = null)
754     {
755         $res = null;
756         try {
757             $res = SugarDateTime::createFromFormat($this->get_date_time_format($user), $date, $this->_getUserTZ($user));
758         } catch (Exception $e) {
759             $GLOBALS['log']->error("fromUser: Conversion of $date exception: {$e->getMessage()}");
760         }
761         if(!($res instanceof DateTime)) {
762             $uf = $this->get_date_time_format($user);
763             $GLOBALS['log']->error("fromUser: Conversion of $date from user format $uf failed");
764             return null;
765         }
766         return $res;
767     }
768
769     /**
770      * Create a date from a certain type of field in user format
771      * The types are: date, time, datatime[combo]
772      * @param string $date the datetime string
773      * @param string $type string type
774      * @param User $user
775      * @return SugarDateTime
776      */
777     public function fromUserType($date, $type, $user = null)
778     {
779         switch($type) {
780             case "date":
781                 return $this->fromUserDate($date, $user);
782                 break;
783             case 'time':
784                 return $this->fromUserTime($date, $user);
785                 break;
786             case 'datetime':
787             case 'datetimecombo':
788             default:
789                 return $this->fromUser($date, $user);
790         }
791     }
792
793     /**
794      * Get DateTime from user time string
795      *
796      * @param string $date
797      * @param User $user
798      * @return SugarDateTime
799      */
800     public function fromUserTime($date, User $user = null)
801     {
802         try {
803             return SugarDateTime::createFromFormat($this->get_time_format($user), $date, $this->_getUserTZ($user));
804         } catch (Exception $e) {
805             $uf = $this->get_time_format($user);
806             $GLOBALS['log']->error("fromUserTime: Conversion of $date from user format $uf failed: {$e->getMessage()}");
807             return null;
808         }
809     }
810
811     /**
812      * Get DateTime from user date string
813          * Usually for calendar-related functions like holidays
814      * Note: by default does not convert tz!
815      * @param string $date
816      * @param bool $convert_tz perform TZ converson?
817      * @param User $user
818      * @return SugarDateTime
819      */
820     public function fromUserDate($date, $convert_tz = false, User $user = null)
821     {
822         try {
823             return SugarDateTime::createFromFormat($this->get_date_format($user), $date, $convert_tz?$this->_getUserTZ($user):self::$gmtTimezone);
824         } catch (Exception $e) {
825             $uf = $this->get_date_format($user);
826             $GLOBALS['log']->error("fromUserDate: Conversion of $date from user format $uf failed: {$e->getMessage()}");
827             return null;
828         }
829     }
830
831     /**
832      * Create a date object from any string
833      *
834      * Same formats accepted as for DateTime ctor
835      *
836      * @param string $date
837      * @param User $user
838      * @return SugarDateTime
839      */
840     public function fromString($date, User $user = null)
841     {
842         try {
843             return new SugarDateTime($date, $this->_getUserTZ($user));
844         } catch (Exception $e) {
845             $GLOBALS['log']->error("fromString: Conversion of $date from string failed: {$e->getMessage()}");
846             return null;
847         }
848     }
849
850     /**
851      * Create DateTime from timestamp
852      *
853      * @param interger|string $ts
854      * @return SugarDateTime
855      */
856     public function fromTimestamp($ts)
857     {
858         return new SugarDateTime("@$ts");
859     }
860
861     /**
862      * Convert DateTime to GMT timezone
863      * @param DateTime $date
864      * @return DateTime
865      */
866     public function tzGMT(DateTime $date)
867     {
868         return $date->setTimezone(self::$gmtTimezone);
869     }
870
871     /**
872      * Convert DateTime to user timezone
873      * @param DateTime $date
874      * @param User $user
875      * @return DateTime
876      */
877     public function tzUser(DateTime $date, User $user = null)
878     {
879         return $date->setTimezone($this->_getUserTZ($user));
880     }
881
882     /**
883      * Get string defining midnight in current user's format
884      * @param string $format Time format to use
885      * @return string
886      */
887     protected function _get_midnight($format = null)
888     {
889         $zero = new DateTime("@0", self::$gmtTimezone);
890         return $zero->format($format?$format:$this->get_time_format());
891     }
892
893     /**
894      *
895      * Basic conversion function
896      *
897      * Converts between two string dates in different formats and timezones
898      *
899      * @param string $date
900      * @param string $fromFormat
901      * @param DateTimeZone $fromTZ
902      * @param string $toFormat
903      * @param DateTimeZone|null $toTZ
904      * @param bool $expand If string lacks time, expand it to include time
905      * @return string
906      */
907     protected function _convert($date, $fromFormat, $fromTZ, $toFormat, $toTZ, $expand = false)
908     {
909         $date = trim($date);
910         if (empty($date)) {
911             return $date;
912         }
913         try {
914             if ($expand && strlen($date) <= 10) {
915                 $date = $this->expandDate($date, $fromFormat);
916             }
917             $phpdate = SugarDateTime::createFromFormat($fromFormat, $date, $fromTZ);
918             if ($phpdate == false) {
919                 $GLOBALS['log']->error("convert: Conversion of $date from $fromFormat to $toFormat failed");
920                 return '';
921             }
922             if ($fromTZ !== $toTZ && $toTZ != null) {
923                 $phpdate->setTimeZone($toTZ);
924             }
925             return $phpdate->format($toFormat);
926         } catch (Exception $e) {
927             $GLOBALS['log']->error("Conversion of $date from $fromFormat to $toFormat failed: {$e->getMessage()}");
928             return '';
929         }
930     }
931
932     /**
933      * Convert DB datetime to local datetime
934      *
935      * TZ conversion is controlled by parameter
936      *
937      * @param string $date Original date in DB format
938      * @param bool $meridiem Ignored for BC
939      * @param bool $convert_tz Perform TZ conversion?
940      * @param User $user User owning the conversion formats
941      * @return string Date in display format
942      */
943     function to_display_date_time($date, $meridiem = true, $convert_tz = true, $user = null)
944     {
945         return $this->_convert($date, self::DB_DATETIME_FORMAT, self::$gmtTimezone, $this->get_date_time_format($user),
946             $convert_tz ? $this->_getUserTZ($user) : self::$gmtTimezone, true);
947     }
948
949     /**
950      * Converts DB time string to local time string
951      *
952      * TZ conversion depends on parameter
953      *
954      * @param string $date Time in DB format
955      * @param bool $meridiem
956      * @param bool $convert_tz Perform TZ conversion?
957      * @return string Time in user-defined format
958      */
959     public function to_display_time($date, $meridiem = true, $convert_tz = true)
960     {
961         if($convert_tz && strpos($date, ' ') === false) {
962             // we need TZ adjustment but have no date, assume today
963             $date = $this->expandTime($date, self::DB_DATETIME_FORMAT, self::$gmtTimezone);
964         }
965         return $this->_convert($date,
966             $convert_tz ? self::DB_DATETIME_FORMAT : self::DB_TIME_FORMAT, self::$gmtTimezone,
967             $this->get_time_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone);
968     }
969
970     /**
971      * Splits time in given format into components
972      *
973      * Components: h, m, s, a (am/pm) if format requires it
974      * If format has am/pm, hour is 12-based, otherwise 24-based
975      *
976      * @param string $date
977      * @param string $format
978      * @return array
979      */
980     public function splitTime($date, $format)
981     {
982         if (! ($date instanceof DateTime)) {
983             $date = SugarDateTime::createFromFormat($format, $date);
984         }
985         $ampm = strpbrk($format, 'aA');
986         $datearr = array(
987                 "h" => ($ampm == false) ? $date->format("H") : $date->format("h"),
988                 'm' => $date->format("i"),
989                 's' => $date->format("s")
990         );
991         if ($ampm) {
992             $datearr['a'] = ($ampm{0} == 'a') ? $date->format("a") : $date->format("A");
993         }
994         return $datearr;
995     }
996
997     /**
998      * Converts DB date string to local date string
999      *
1000      * TZ conversion depens on parameter
1001      *
1002      * @param string $date Date in DB format
1003      * @param bool $convert_tz Perform TZ conversion?
1004      * @return string Date in user-defined format
1005      */
1006     public function to_display_date($date, $convert_tz = true)
1007     {
1008         return $this->_convert($date,
1009             self::DB_DATETIME_FORMAT, self::$gmtTimezone,
1010             $this->get_date_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone, true);
1011     }
1012
1013     /**
1014      * Convert date from format to format
1015      *
1016      * No TZ conversion is performed!
1017      *
1018      * @param string $date
1019      * @param string $from Source format
1020      * @param string $to Destination format
1021      * @return string Converted date
1022      */
1023     function to_display($date, $from, $to)
1024     {
1025         return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
1026     }
1027
1028     /**
1029      * Get DB datetime format
1030      * @return string
1031      */
1032     public function get_db_date_time_format()
1033     {
1034         return self::DB_DATETIME_FORMAT;
1035     }
1036
1037     /**
1038      * Get DB date format
1039      * @return string
1040      */
1041     public function get_db_date_format()
1042     {
1043         return self::DB_DATE_FORMAT;
1044     }
1045
1046     /**
1047      * Get DB time format
1048      * @return string
1049      */
1050     public function get_db_time_format()
1051     {
1052         return self::DB_TIME_FORMAT;
1053     }
1054
1055     /**
1056      * Convert date from local datetime to GMT-based DB datetime
1057      *
1058      * Includes TZ conversion.
1059      *
1060      * @param string $date
1061      * @return string Datetime in DB format
1062      */
1063     public function to_db($date)
1064     {
1065         return $this->_convert($date,
1066             $this->get_date_time_format(), $this->_getUserTZ(),
1067             $this->get_db_date_time_format(), self::$gmtTimezone,
1068             true);
1069     }
1070
1071     /**
1072      * Convert local datetime to DB date
1073      *
1074      * TZ conversion depends on parameter. If false, only format conversion is performed.
1075      *
1076      * @param string $date Local date
1077      * @param bool $convert_tz Should time and TZ be taken into account?
1078      * @return string Date in DB format
1079      */
1080     public function to_db_date($date, $convert_tz = true)
1081     {
1082         return $this->_convert($date,
1083             $this->get_date_time_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone,
1084             self::DB_DATE_FORMAT, self::$gmtTimezone, true);
1085     }
1086
1087     /**
1088      * Convert local datetime to DB time
1089      *
1090      * TZ conversion depends on parameter. If false, only format conversion is performed.
1091      *
1092      * @param string $date Local date
1093      * @param bool $convert_tz Should time and TZ be taken into account?
1094      * @return string Time in DB format
1095      */
1096     public function to_db_time($date, $convert_tz = true)
1097     {
1098         $format = $this->get_date_time_format();
1099         $tz = $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone;
1100         if($convert_tz && strpos($date, ' ') === false) {
1101             // we need TZ adjustment but have short string, expand it to full one
1102             // FIXME: if the string is short, should we assume date or time?
1103             $date = $this->expandTime($date, $format, $tz);
1104         }
1105         return $this->_convert($date,
1106             $convert_tz ? $format : $this->get_time_format(),
1107             $tz,
1108             self::DB_TIME_FORMAT, self::$gmtTimezone);
1109     }
1110
1111     /**
1112      * Takes a Date & Time value in local format and converts them to DB format
1113      * No TZ conversion!
1114      *
1115      * @param string $date
1116      * @param string $time
1117      * @return array Date & time in DB format
1118      **/
1119     public function to_db_date_time($date, $time)
1120     {
1121         try {
1122             $phpdate = SugarDateTime::createFromFormat($this->get_date_time_format(),
1123                 $this->merge_date_time($date, $time), self::$gmtTimezone);
1124             if ($phpdate == false) {
1125                 return array('', '');
1126             }
1127             return array($this->asDbDate($phpdate), $this->asDbTime($phpdate));
1128         } catch (Exception $e) {
1129             $GLOBALS['log']->error("Conversion of $date,$time failed");
1130             return array('', '');
1131         }
1132     }
1133
1134     /**
1135      * Return current time in DB format
1136      * @return string
1137      */
1138     public function nowDb()
1139     {
1140         if(!$this->allow_cache) {
1141             $nowGMT = $this->getNow();
1142         } else {
1143             $nowGMT = $this->now;
1144         }
1145         return $this->asDb($nowGMT);
1146     }
1147
1148     /**
1149      * Return current date in DB format
1150      * @return string
1151      */
1152     public function nowDbDate()
1153     {
1154         if(!$this->allow_cache) {
1155             $nowGMT = $this->getNow();
1156         } else {
1157             $nowGMT = $this->now;
1158         }
1159         return $this->asDbDate($nowGMT, true);
1160     }
1161
1162     /**
1163      * Get 'now' DateTime object
1164      * @param bool $userTz return in user timezone?
1165      * @return SugarDateTime
1166      */
1167     public function getNow($userTz = false)
1168     {
1169         if(!$this->allow_cache) {
1170             return new SugarDateTime("now", $userTz?$this->_getUserTz():self::$gmtTimezone);
1171         }
1172         // TODO: should we return clone?
1173         $now = clone $this->now;
1174         if($userTz) {
1175             return $this->tzUser($now);
1176         }
1177         return $now;
1178     }
1179
1180     /**
1181      * Set 'now' time
1182      * For testability - predictable time value
1183      * @param DateTime $now
1184      * @return TimeDate $this
1185      */
1186     public function setNow($now)
1187     {
1188         $this->now = $now;
1189         return $this;
1190     }
1191
1192     /**
1193      * Return current datetime in local format
1194      * @return string
1195      */
1196     public function now()
1197     {
1198         return  $this->asUser($this->getNow());
1199     }
1200
1201     /**
1202      * Return current date in User format
1203      * @return string
1204      */
1205     public function nowDate()
1206     {
1207         return  $this->asUserDate($this->getNow());
1208     }
1209
1210     /**
1211      * Get user format's time separator
1212      * @return string
1213      */
1214     public function timeSeparator()
1215     {
1216         if (empty($this->time_separator)) {
1217             $this->time_separator = $this->timeSeparatorFormat($this->get_time_format());
1218         }
1219         return $this->time_separator;
1220     }
1221
1222     /**
1223      * Find out format's time separator
1224      * @param string $timeformat Time format
1225      * @return stringS
1226      */
1227     public function timeSeparatorFormat($timeformat)
1228     {
1229         $date = $this->_convert("00:11:22", self::DB_TIME_FORMAT, null, $timeformat, null);
1230         if (preg_match('/\d+(.+?)11/', $date, $matches)) {
1231             $sep = $matches[1];
1232         } else {
1233             $sep = ':';
1234         }
1235         return $sep;
1236     }
1237
1238     /**
1239      * Returns start and end of a certain local date in GMT
1240      * Example: for May 19 in PDT start would be 2010-05-19 07:00:00, end would be 2010-05-20 06:59:59
1241      * @param string|DateTime $date Date in any suitable format
1242      * @param User $user
1243      * @return array Start & end date in start, startdate, starttime, end, enddate, endtime
1244      */
1245     public function getDayStartEndGMT($date, User $user = null)
1246     {
1247         if ($date instanceof DateTime) {
1248             $min = clone $date;
1249             $min->setTimezone($this->_getUserTZ($user));
1250             $max = clone $date;
1251             $max->setTimezone($this->_getUserTZ($user));
1252         } else {
1253             $min = new DateTime($date, $this->_getUserTZ($user));
1254             $max = new DateTime($date, $this->_getUserTZ($user));
1255         }
1256         $min->setTime(0, 0);
1257         $max->setTime(23, 59, 59);
1258
1259         $min->setTimezone(self::$gmtTimezone);
1260         $max->setTimezone(self::$gmtTimezone);
1261
1262         $result['start'] = $this->asDb($min);
1263         $result['startdate'] = $this->asDbDate($min);
1264         $result['starttime'] = $this->asDbTime($min);
1265         $result['end'] = $this->asDb($max);
1266         $result['enddate'] = $this->asDbDate($max);
1267         $result['endtime'] = $this->asDbtime($max);
1268
1269         return $result;
1270     }
1271
1272     /**
1273      * Expand date format by adding midnight to it
1274      * Note: date is assumed to be in target format already
1275      * @param string $date
1276      * @param string $format Target format
1277      * @return string
1278      */
1279     public function expandDate($date, $format)
1280     {
1281         $formats = $this->split_date_time($format);
1282         if(isset($formats[1])) {
1283             return $this->merge_date_time($date, $this->_get_midnight($formats[1]));
1284         }
1285         return $date;
1286     }
1287
1288     /**
1289      * Expand time format by adding today to it
1290      * Note: time is assumed to be in target format already
1291      * @param string $date
1292      * @param string $format Target format
1293      * @param DateTimeZone $tz
1294      * @return string
1295      */
1296     public function expandTime($date, $format, $tz)
1297     {
1298         $formats = $this->split_date_time($format);
1299         if(isset($formats[1])) {
1300             $now = clone $this->getNow();
1301             $now->setTimezone($tz);
1302             return $this->merge_date_time($now->format($formats[0]), $date);
1303         }
1304         return $date;
1305     }
1306
1307     /**
1308          * Get midnight (start of the day) in local time format
1309          *
1310          * @return Time string
1311          */
1312         function get_default_midnight()
1313         {
1314         return $this->_get_midnight($this->get_time_format());
1315         }
1316
1317         /**
1318          * Get the name of the timezone for the user
1319          * @param User $user User, default - current user
1320          * @return string
1321          */
1322         public static function userTimezone(User $user = null)
1323         {
1324             $user = self::getInstance()->_getUser($user);
1325             if(empty($user)) {
1326                 return '';
1327             }
1328             $tz = self::getInstance()->_getUserTZ($user);
1329             if($tz) {
1330                 return $tz->getName();
1331             }
1332             return '';
1333         }
1334
1335     /**
1336      * Guess the timezone for the current user
1337      * @param int $userOffset Offset from GMT in minutes
1338      * @return string
1339      */
1340         public static function guessTimezone($userOffset = 0)
1341         {
1342             if(!is_numeric($userOffset)) {
1343                     return '';
1344             }
1345             $defaultZones= array(
1346                 'America/Anchorage', 'America/Los_Angeles', 'America/Phoenix', 'America/Chicago',
1347                 'America/New_York', 'America/Argentina/Buenos_Aires', 'America/Montevideo',
1348                 'Europe/London', 'Europe/Amsterdam', 'Europe/Athens', 'Europe/Moscow',
1349                 'Asia/Tbilisi', 'Asia/Omsk', 'Asia/Jakarta', 'Asia/Hong_Kong',
1350                 'Asia/Tokyo', 'Pacific/Guam', 'Australia/Sydney', 'Australia/Perth',
1351             );
1352
1353             $now = new DateTime();
1354             $tzlist = timezone_identifiers_list();
1355             if($userOffset == 0) {
1356              $gmtOffset = date('Z');
1357                  $nowtz = date('e');
1358                  if(in_array($nowtz, $tzlist)) {
1359                  array_unshift($defaultZones, $nowtz);
1360                  } else {
1361                      $nowtz = timezone_name_from_abbr(date('T'), $gmtOffset, date('I'));
1362                      if(in_array($nowtz, $tzlist)) {
1363                          array_unshift($defaultZones, $nowtz);
1364                      }
1365                  }
1366         } else {
1367             $gmtOffset = $userOffset * 60;
1368         }
1369         foreach($defaultZones as $zoneName) {
1370                 $tz = new DateTimeZone($zoneName);
1371                 if($tz->getOffset($now) == $gmtOffset) {
1372                 return $tz->getName();
1373                 }
1374             }
1375         // try all zones
1376             foreach($tzlist as $zoneName) {
1377                 $tz = new DateTimeZone($zoneName);
1378                 if($tz->getOffset($now) == $gmtOffset) {
1379                 return $tz->getName();
1380                 }
1381             }
1382             return null;
1383         }
1384
1385     /**
1386      * Get the description of the user timezone for specific date
1387      * Like: PST(+08:00)
1388      * We need the date because it can be DST or non-DST
1389      * Note it's different from TZ name in tzName() that relates to current date
1390      * @param DateTime $date Current date
1391      * @param User $user User, default - current user
1392      * @return string
1393      */
1394         public static function userTimezoneSuffix(DateTime $date, User $user = null)
1395         {
1396             $user = self::getInstance()->_getUser($user);
1397             if(empty($user)) {
1398                 return '';
1399             }
1400             self::getInstance()->tzUser($date, $user);
1401             return $date->format('T(P)');
1402         }
1403
1404         /**
1405          * Get display name for a certain timezone
1406          * Note: it uses current date for GMT offset, so it may be not suitable for displaying generic dates
1407          * @param string|DateTimeZone $name TZ name
1408          * @return string
1409          */
1410         public static function tzName($name)
1411         {
1412             if(empty($name)) {
1413                 return '';
1414             }
1415             if($name instanceof DateTimeZone) {
1416                 $tz = $name;
1417             } else {
1418             $tz = timezone_open($name);
1419             }
1420         if(!$tz) {
1421             return "???";
1422         }
1423         $now = new DateTime("now", $tz);
1424         $off = $now->getOffset();
1425         $translated = translate('timezone_dom','',$name);
1426         if(is_string($translated) && !empty($translated)) {
1427             $name = $translated;
1428         }
1429         return sprintf("%s (GMT%+2d:%02d)%s", str_replace('_',' ', $name), $off/3600, (abs($off)/60)%60, "");//$now->format('I')==1?"(+DST)":"");
1430         }
1431
1432
1433     /**
1434      * Timezone sorting helper
1435      * Sorts by name
1436      * @param array $a
1437      * @param array $b
1438      * @internal
1439      * @return int
1440      */
1441         public static function _sortTz($a, $b)
1442         {
1443             if($a[0] == $b[0]) {
1444             return strcmp($a[1], $b[1]);
1445             } else {
1446                 return $a[0]<$b[0]?-1:1;
1447             }
1448         }
1449
1450         /**
1451          * Get list of all timezones in the system
1452          * @return array
1453          */
1454         public static function getTimezoneList()
1455         {
1456         $now = new DateTime();
1457         $res_zones = $zones = array();
1458             foreach(timezone_identifiers_list() as $zoneName) {
1459             $tz = new DateTimeZone($zoneName);
1460                 $zones[$zoneName] = array($tz->getOffset($now), self::tzName($zoneName));
1461             }
1462             uasort($zones, array('TimeDate', '_sortTz'));
1463             foreach($zones as $name => $zonedef) {
1464                 $res_zones[$name] = $zonedef[1];
1465             }
1466             return $res_zones;
1467         }
1468
1469     /**
1470      * Print timestamp in RFC2616 format:
1471      * @param int|null $ts Null means current ts
1472      * @return string
1473      */
1474         public static function httpTime($ts = null)
1475         {
1476             if($ts === null) {
1477                 $ts = time();
1478             }
1479             return gmdate(self::RFC2616_FORMAT, $ts);
1480         }
1481
1482         /**
1483          * Create datetime object from calendar array
1484          * @param array $time
1485          * @return SugarDateTime
1486          */
1487         public function fromTimeArray($time)
1488         {
1489                 if (! isset( $time) || count($time) == 0 )
1490                 {
1491                         return $this->nowDb();
1492                 }
1493                 elseif ( isset( $time['ts']))
1494                 {
1495                         return $this->fromTimestamp($time['ts']);
1496                 }
1497                 elseif ( isset( $time['date_str']))
1498                 {
1499                     return $this->fromDb($time['date_str']);
1500                 }
1501                 else
1502                 {
1503                 $hour = 0;
1504                 $min = 0;
1505                 $sec = 0;
1506                 $now = $this->getNow(true);
1507                 $day = $now->day;
1508                 $month = $now->month;
1509                 $year = $now->year;
1510                     if (isset($time['sec']))
1511                         {
1512                                 $sec = $time['sec'];
1513                         }
1514                         if (isset($time['min']))
1515                         {
1516                                 $min = $time['min'];
1517                         }
1518                         if (isset($time['hour']))
1519                         {
1520                                 $hour = $time['hour'];
1521                         }
1522                         if (isset($time['day']))
1523                         {
1524                                 $day = $time['day'];
1525                         }
1526                         if (isset($time['month']))
1527                         {
1528                                 $month = $time['month'];
1529                         }
1530                         if (isset($time['year']) && $time['year'] >= 1970)
1531                         {
1532                                 $year = $time['year'];
1533                         }
1534                         return $now->setDate($year, $month, $day)->setTime($hour, $min, $sec)->setTimeZone(self::$gmtTimezone);
1535                 }
1536         return null;
1537         }
1538
1539         /**
1540          * Returns the date portion of a datetime string
1541          *
1542          * @param string $datetime
1543          * @return string
1544          */
1545         public function getDatePart($datetime)
1546         {
1547             list($date, $time) = $this->split_date_time($datetime);
1548             return $date;
1549         }
1550
1551         /**
1552          * Returns the time portion of a datetime string
1553          *
1554          * @param string $datetime
1555          * @return string
1556          */
1557         public function getTimePart($datetime)
1558         {
1559             list($date, $time) = $this->split_date_time($datetime);
1560             return $time;
1561         }
1562
1563     /**
1564      * Returns the offset from user's timezone to GMT
1565      * @param User $user
1566      * @param DateTime $time When the offset is taken, default is now
1567      * @return int Offset in minutes
1568      */
1569     public function getUserUTCOffset(User $user = null, DateTime $time = null)
1570     {
1571         if(empty($time)) {
1572             $time = $this->now;
1573         }
1574         return $this->_getUserTZ($user)->getOffset($time) / 60;
1575     }
1576
1577     /**
1578      * Create regexp from datetime format
1579      * @param string $format
1580      * @return string Regular expression string
1581      */
1582     public static function get_regular_expression($format)
1583     {
1584         $newFormat = '';
1585         $regPositions = array();
1586         $ignoreNextChar = false;
1587         $count = 1;
1588         foreach (str_split($format) as $char) {
1589             if (! $ignoreNextChar && isset(self::$format_to_regexp[$char])) {
1590                 $newFormat .= '(' . self::$format_to_regexp[$char] . ')';
1591                 $regPositions[$char] = $count;
1592                 $count ++;
1593             } else {
1594                 $ignoreNextChar = false;
1595                 $newFormat .= $char;
1596
1597             }
1598             if ($char == "\\") {
1599                 $ignoreNextChar = true;
1600             }
1601         }
1602
1603         return array('format' => $newFormat, 'positions' => $regPositions);
1604     }
1605
1606     // format - date expression ('' means now) for start and end of the range
1607     protected $date_expressions = array(
1608         'yesterday' =>    array("-1 day", "-1 day"),
1609         'today' =>        array("", ""),
1610         'tomorrow' =>     array("+1 day", "+1 day"),
1611         'last_7_days' =>  array("-6 days", ""),
1612         'next_7_days' =>  array("", "+6 days"),
1613         'last_30_days' => array("-29 days", ""),
1614         'next_30_days' => array("", "+29 days"),
1615     );
1616
1617     /**
1618      * Parse date template
1619      * @internal
1620      * @param string $template Date expression
1621      * @param bool $daystart Do we want start or end of the day?
1622      * @param User $user
1623      * @param bool $adjustForTimezone
1624      * @return SugarDateTime
1625      */
1626     protected function parseFromTemplate($template, $daystart, User $user = null, $adjustForTimezone = true)
1627         {
1628         $rawTime = $this->getNow();
1629         $now = $adjustForTimezone?$this->tzUser($rawTime, $user):$rawTime;
1630         if(!empty($template)) {
1631             $now->modify($template);
1632         }
1633         if($daystart) {
1634             return $now->get_day_begin();
1635         } else {
1636             return $now->get_day_end();
1637         }
1638         }
1639
1640     /**
1641      * Get month-long range mdiff months from now
1642      * @internal
1643      * @param int $mdiff
1644      * @param User $user
1645      * @param bool $adjustForTimezone
1646      * @return array
1647      */
1648         protected function diffMon($mdiff, User $user = null, $adjustForTimezone = true)
1649         {
1650         $rawTime = $this->getNow();
1651         $now = $adjustForTimezone?$this->tzUser($rawTime, $user):$rawTime;
1652             $now->setDate($now->year, $now->month+$mdiff, 1);
1653             $start = $now->get_day_begin();
1654             $end = $now->setDate($now->year, $now->month, $now->days_in_month)->setTime(23, 59, 59);
1655             return array($start, $end);
1656         }
1657
1658     /**
1659      * Get year-long range ydiff years from now
1660      * @internal
1661      * @param int $ydiff
1662      * @param User $user
1663      * @param bool $adjustForTimezone
1664      * @return array
1665      */
1666         protected function diffYear($ydiff, User $user = null, $adjustForTimezone = true)
1667         {
1668         $rawTime = $this->getNow();
1669         $now = $adjustForTimezone?$this->tzUser($rawTime, $user):$rawTime;
1670         $now->setDate($now->year+$ydiff, 1, 1);
1671             $start = $now->get_day_begin();
1672             $end = $now->setDate($now->year, 12, 31)->setTime(23, 59, 59);
1673             return array($start, $end);
1674         }
1675
1676         /**
1677          * Parse date range expression
1678          * Returns beginning and end of the range as a date
1679          * @param string $range
1680          * @param User $user
1681      * @param bool $adjustForTimezone Do we need to adjust for timezone?
1682          * @return array of two Date objects, start & end
1683          */
1684         public function parseDateRange($range, User $user = null, $adjustForTimezone = true)
1685         {
1686         if(isset($this->date_expressions[$range])) {
1687             return array($this->parseFromTemplate($this->date_expressions[$range][0], true, $user, $adjustForTimezone),
1688                 $this->parseFromTemplate($this->date_expressions[$range][1], false, $user, $adjustForTimezone)
1689             );
1690         }
1691             switch($range) {
1692                         case 'next_month':
1693                             return $this->diffMon(1,  $user, $adjustForTimezone);
1694                     case 'last_month':
1695                             return $this->diffMon(-1,  $user, $adjustForTimezone);
1696                     case 'this_month':
1697                             return $this->diffMon(0,  $user, $adjustForTimezone);
1698                 case 'last_year':
1699                             return $this->diffYear(-1,  $user, $adjustForTimezone);
1700                 case 'this_year':
1701                             return $this->diffYear(0,  $user, $adjustForTimezone);
1702                 case 'next_year':
1703                             return $this->diffYear(1,  $user, $adjustForTimezone);
1704                 default:
1705                             return null;
1706             }
1707         }
1708
1709     /********************* OLD functions, should not be used publicly anymore ****************/
1710     /**
1711      * Merge time without am/pm with am/pm string
1712      * @TODO find better way to do this!
1713      * @deprecated for public use
1714      * @param string $date
1715      * @param string $format User time format
1716      * @param string $mer
1717      * @return string
1718      */
1719     function merge_time_meridiem($date, $format, $mer)
1720     {
1721         $date = trim($date);
1722         if (empty($date)) {
1723             return $date;
1724         }
1725         $fakeMerFormat = str_replace(array('a', 'A'), array('@~@', '@~@'), $format);
1726         $noMerFormat = str_replace(array('a', 'A'), array('', ''), $format);
1727         $newDate = $this->swap_formats($date, $noMerFormat, $fakeMerFormat);
1728         return str_replace('@~@', $mer, $newDate);
1729     }
1730
1731     /**
1732      * @deprecated for public use
1733      * Convert date from one format to another
1734      *
1735      * @param string $date
1736      * @param string $from
1737      * @param string $to
1738      * @return string
1739      */
1740     public function swap_formats($date, $from, $to)
1741     {
1742         return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
1743     }
1744
1745     /**
1746      * @deprecated for public use
1747      * handles offset values for Timezones and DST
1748      * @param   $date        string             date/time formatted in user's selected format
1749      * @param   $format      string             destination format value as passed to PHP's date() funtion
1750      * @param   $to                  boolean
1751      * @param   $user        object             user object from which Timezone and DST
1752      * @param   $usetimezone string             timezone name
1753      * values will be derived
1754      * @return   string         date formatted and adjusted for TZ and DST
1755      */
1756     function handle_offset($date, $format, $to = true, $user = null, $usetimezone = null)
1757     {
1758         $tz = empty($usetimezone)?$this->_getUserTZ($user):new DateTimeZone($usetimezone);
1759         $dateobj = new SugarDateTime($date, $to? self::$gmtTimezone : $tz);
1760         $dateobj->setTimezone($to ? $tz: self::$gmtTimezone);
1761         return $dateobj->format($format);
1762 //        return $this->_convert($date, $format, $to ? self::$gmtTimezone : $tz, $format, $to ? $tz : self::$gmtTimezone);
1763     }
1764
1765     /**
1766      * @deprecated for public use
1767      * Get current GMT datetime in DB format
1768      * @return string
1769      */
1770     function get_gmt_db_datetime()
1771     {
1772         return $this->nowDb();
1773     }
1774
1775     /**
1776      * @deprecated for public use
1777      * Get current GMT date in DB format
1778      * @return string
1779      */
1780     function get_gmt_db_date()
1781     {
1782         return $this->nowDbDate();
1783     }
1784
1785     /**
1786      * @deprecated for public use
1787      * this method will take an input $date variable (expecting Y-m-d format)
1788      * and get the GMT equivalent - with an hour-level granularity :
1789      * return the max value of a given locale's
1790      * date+time in GMT metrics (i.e., if in PDT, "2005-01-01 23:59:59" would be
1791      * "2005-01-02 06:59:59" in GMT metrics)
1792      * @param $date
1793      * @return array
1794      */
1795     function handleOffsetMax($date)
1796     {
1797         $min = new DateTime($date, $this->_getUserTZ());
1798         $min->setTime(0, 0);
1799         $max = new DateTime($date, $this->_getUserTZ());
1800         $max->setTime(23, 59, 59);
1801
1802         $min->setTimezone(self::$gmtTimezone);
1803         $max->setTimezone(self::$gmtTimezone);
1804
1805         $gmtDateTime['date'] = $this->asDbDate($max, false);
1806         $gmtDateTime['time'] = $this->asDbDate($max, false);
1807         $gmtDateTime['min'] = $this->asDb($min);
1808         $gmtDateTime['max'] = $this->asDb($max);
1809
1810         return $gmtDateTime;
1811     }
1812
1813     /**
1814      * @deprecated for public use
1815      * this returns the adjustment for a user against the server time
1816      *
1817      * @return integer number of minutes to adjust a time by to get the appropriate time for the user
1818      */
1819     public function adjustmentForUserTimeZone()
1820     {
1821         $tz = $this->_getUserTZ();
1822         $server_tz = new DateTimeZone(date_default_timezone_get());
1823         if ($tz && $server_tz) {
1824             return ($server_tz->getOffset($this->now) - $tz->getOffset($this->now)) / 60;
1825         }
1826         return 0;
1827     }
1828
1829     /**
1830      * @deprecated for public use
1831      * assumes that olddatetime is in Y-m-d H:i:s format
1832      * @param $olddatetime
1833      * @return string
1834      */
1835     function convert_to_gmt_datetime($olddatetime)
1836     {
1837         if (! empty($olddatetime)) {
1838             return date('Y-m-d H:i:s', strtotime($olddatetime) - date('Z'));
1839         }
1840         return '';
1841     }
1842
1843     /**
1844      * @deprecated for public use
1845      * get user timezone info
1846      * @param User $user
1847      * @return array
1848      */
1849     public function getUserTimeZone(User $user = null)
1850     {
1851         $tz = $this->_getUserTZ($user);
1852         return array("gmtOffset" => $tz->getOffset($this->now) / 60);
1853     }
1854
1855     /**
1856      * @deprecated for public use
1857      * get timezone start & end
1858      * @param $year
1859      * @param string $zone
1860      * @return array
1861      */
1862     public function getDSTRange($year, $zone = null)
1863     {
1864         if(!empty($zone)) {
1865                 $tz = timezone_open($zone);
1866         }
1867         if(empty($tz)) {
1868                 $tz = $this->_getUserTZ();
1869         }
1870
1871         $year_date = SugarDateTime::createFromFormat("Y", $year, self::$gmtTimezone);
1872         $year_end = clone $year_date;
1873         $year_end->setDate((int) $year, 12, 31);
1874         $year_end->setTime(23, 59, 59);
1875         $year_date->setDate((int) $year, 1, 1);
1876         $year_date->setTime(0, 0, 0);
1877                 $result = array();
1878         $transitions = $tz->getTransitions($year_date->ts, $year_end->ts);
1879         $idx = 0;
1880         if(version_compare(PHP_VERSION, '5.3.0', '<')) {
1881                 // <5.3.0 ignores parameters, advance manually to current year
1882                 $start_ts = $year_date->ts;
1883                 while(isset($transitions[$idx]) && $transitions[$idx]["ts"] < $start_ts) $idx++;
1884         }
1885         // get DST start
1886         while (isset($transitions[$idx]) && !$transitions[$idx]["isdst"]) $idx++;
1887         if(isset($transitions[$idx])) {
1888                 $result["start"] = $this->fromTimestamp($transitions[$idx]["ts"])->asDb();
1889         }
1890         // get DST end
1891         while (isset($transitions[$idx]) && $transitions[$idx]["isdst"]) $idx++;
1892         if(isset($transitions[$idx])) {
1893                 $result["end"] = $this->fromTimestamp($transitions[$idx]["ts"])->asDb();
1894         }
1895         return $result;
1896     }
1897
1898 /****************** GUI stuff that really shouldn't be here, will be moved ************/
1899     /**
1900      * Get Javascript variables setup for user date format validation
1901      * @deprecated moved to SugarView
1902      * @return string JS code
1903      */
1904     function get_javascript_validation()
1905     {
1906         return SugarView::getJavascriptValidation();
1907     }
1908
1909     /**
1910      * AMPMMenu
1911      * This method renders a SELECT HTML form element based on the
1912      * user's time format preferences, with give date's value highlighted.
1913      *
1914      * If user's prefs have no AM/PM string, returns empty string.
1915      *
1916      * @todo There is hardcoded HTML in here that does not allow for localization
1917      * of the AM/PM am/pm Strings in this drop down menu.  Also, perhaps
1918      * change to the substr_count function calls to strpos
1919      * TODO: Remove after full switch to fields
1920      * @deprecated
1921      * @param string $prefix Prefix for SELECT
1922      * @param string $date Date in display format
1923      * @param string $attrs Additional attributes for SELECT
1924      * @return string SELECT HTML
1925      */
1926     function AMPMMenu($prefix, $date, $attrs = '')
1927     {
1928         $tf = $this->get_time_format();
1929         $am = strpbrk($tf, 'aA');
1930         if ($am == false) {
1931             return '';
1932         }
1933         $selected = array("am" => "", "pm" => "", "AM" => "", "PM" => "");
1934         if (preg_match('/([ap]m)/i', $date, $match)) {
1935             $selected[$match[1]] = " selected";
1936         }
1937
1938         $menu = "<select name='" . $prefix . "meridiem' " . $attrs . ">";
1939         if ($am{0} == 'a') {
1940             $menu .= "<option value='am'{$selected["am"]}>am";
1941             $menu .= "<option value='pm'{$selected["pm"]}>pm";
1942         } else {
1943             $menu .= "<option value='AM'{$selected["AM"]}>AM";
1944             $menu .= "<option value='PM'{$selected["PM"]}>PM";
1945         }
1946
1947         return $menu . '</select>';
1948     }
1949
1950     /**
1951      * Get user format in JS form
1952      * TODO: Remove after full switch to fields
1953      * @return string
1954      */
1955     function get_user_date_format()
1956     {
1957         return str_replace(array('Y', 'm', 'd'), array('yyyy', 'mm', 'dd'), $this->get_date_format());
1958     }
1959
1960     /**
1961      * Get user time format example
1962      * TODO: Remove after full switch to fields
1963      * @deprecated
1964      * @return string
1965      */
1966     function get_user_time_format()
1967     {
1968         global $sugar_config;
1969         $time_pref = $this->get_time_format();
1970
1971         if (! empty($time_pref) && ! empty($sugar_config['time_formats'][$time_pref])) {
1972             return $sugar_config['time_formats'][$time_pref];
1973         }
1974
1975         return '23:00'; //default
1976     }
1977
1978 }