2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
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.
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
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
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.
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.
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 ********************************************************************************/
38 require_once 'include/SugarDateTime.php';
42 * New Time & Date handling class
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.
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';
56 // Standard DB date/time formats
57 // they are constant, vars are for compatibility
58 public $dbDayFormat = self::DB_DATE_FORMAT;
59 public $dbTimeFormat = self::DB_TIME_FORMAT;
62 * Regexp for matching format elements
65 protected static $format_to_regexp = array(
84 * Relation between date() and strftime() formats
87 public static $format_to_str = array(
112 * GMT timezone object
116 protected static $gmtTimezone;
136 protected $current_user_id;
141 protected $current_user_tz;
144 * Separator for current user time format
148 protected $time_separator;
151 * Always consider user TZ to be GMT and date format DB format - for SOAP etc.
155 protected $always_db = false;
158 * Global instance of TimeDate
161 protected static $timedate;
163 public $allow_cache = true;
165 public function __construct(User $user = null)
167 if (self::$gmtTimezone == null) {
168 self::$gmtTimezone = new DateTimeZone("UTC");
170 $this->now = new SugarDateTime();
171 $this->tzGMT($this->now);
176 * Set flag specifying we should always use DB format
180 public function setAlwaysDb($flag = true)
182 $this->always_db = $flag;
188 * Get "always use DB format" flag
191 public function isAlwaysDb()
193 return !empty($GLOBALS['disable_date_format']) || $this->always_db;
197 * Get TimeDate instance
200 public static function getInstance()
202 if(empty(self::$timedate)) {
203 if(ini_get('date.timezone') == '') {
204 // Remove warning about default timezone
205 date_default_timezone_set(@date('e'));
207 $tz = self::guessTimezone();
208 } catch(Exception $e) {
209 $tz = "UTC"; // guess failed, switch to UTC
211 if(isset($GLOBALS['log'])) {
212 $GLOBALS['log']->fatal("Configuration variable date.timezone is not set, guessed timezone $tz. Please set date.timezone=\"$tz\" in php.ini!");
214 date_default_timezone_set($tz);
216 self::$timedate = new self;
218 return self::$timedate;
222 * Set current user for this object
227 public function setUser(User $user = null)
235 * Figure out what the required user is
237 * The order is: supplied parameter, TimeDate's user, global current user
242 protected function _getUser(User $user = null)
248 $user = $GLOBALS['current_user'];
254 * Get timezone for the specified user
257 * @return DateTimeZone
259 protected function _getUserTZ(User $user = null)
261 $user = $this->_getUser($user);
263 if (empty($user) || $this->isAlwaysDb()) {
264 return self::$gmtTimezone;
267 if ($this->allow_cache && $user->id == $this->current_user_id && ! empty($this->current_user_tz)) {
268 // current user is cached
269 return $this->current_user_tz;
273 $usertimezone = $user->getPreference('timezone');
274 if(empty($usertimezone)) {
275 return self::$gmtTimezone;
277 $tz = new DateTimeZone($usertimezone);
278 } catch (Exception $e) {
279 $GLOBALS['log']->fatal('Unknown timezone: ' . $usertimezone);
280 return self::$gmtTimezone;
283 if (empty($this->current_user_id)) {
284 $this->current_user_id = $user->id;
285 $this->current_user_tz = $tz;
292 * Clears all cached data regarding current user
294 public function clearCache()
296 $this->current_user_id = null;
297 $this->current_user_tz = null;
298 $this->time_separator = null;
299 $this->now = new SugarDateTime();
303 * Get user date format.
306 * @param [User] $user user object, current user if not specified
309 public function get_date_format(User $user = null)
311 $user = $this->_getUser($user);
313 if (empty($user) || $this->isAlwaysDb()) {
314 return self::DB_DATE_FORMAT;
317 $datef = $user->getPreference('datef');
318 if(empty($datef) && isset($GLOBALS['current_user']) && $GLOBALS['current_user'] !== $user) {
319 // if we got another user and it has no date format, try current user
320 $datef = $GLOBALS['current_user']->getPreference('datef');
323 $datef = $GLOBALS['sugar_config']['default_date_format'];
333 * Get user time format.
336 * @param User $user user object, current user if not specified
339 public function get_time_format(/*User*/ $user = null)
341 if(is_bool($user) || func_num_args() > 1) {
342 // BC dance - old signature was boolean, User
343 $GLOBALS['log']->fatal('TimeDate::get_time_format(): Deprecated API used, please update you code - get_time_format() now has one argument of type User');
344 if(func_num_args() > 1) {
345 $user = func_get_arg(1);
350 $user = $this->_getUser($user);
352 if (empty($user) || $this->isAlwaysDb()) {
353 return self::DB_TIME_FORMAT;
356 $timef = $user->getPreference('timef');
357 if(empty($timef) && isset($GLOBALS['current_user']) && $GLOBALS['current_user'] !== $user) {
358 // if we got another user and it has no time format, try current user
359 $timef = $GLOBALS['current_user']->getPreference('$timef');
362 $timef = $GLOBALS['sugar_config']['default_time_format'];
371 * Get user datetime format.
374 * @param [User] $user user object, current user if not specified
377 public function get_date_time_format(User $user = null)
379 return $this->merge_date_time($this->get_date_format($user), $this->get_time_format($user));
383 * Make one datetime string from date string and time string
385 * @param string $date
386 * @param string $time
387 * @return string New datetime string
389 function merge_date_time($date, $time)
391 return $date . ' ' . $time;
395 * Split datetime string into date & time
397 * @param string $datetime
400 function split_date_time($datetime)
402 return explode(' ', $datetime);
405 function get_cal_date_format()
407 return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_format());
410 function get_cal_time_format()
412 return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_time_format());
415 function get_cal_date_time_format()
417 return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_time_format());
421 * Verify if the date string conforms to a format
423 * @param string $date
424 * @param string $format Format to check
425 * @param string $toformat
426 * @return bool Is the date ok?
428 public function check_matching_format($date, $format)
431 $dt = SugarDateTime::createFromFormat($format, $date);
432 if (!is_object($dt)) {
435 } catch (Exception $e) {
442 * Format DateTime object as DB datetime
444 * @param DateTime $date
447 public function asDb(DateTime $date)
449 $date->setTimezone(self::$gmtTimezone);
450 return $date->format($this->get_db_date_time_format());
454 * Format date as DB-formatted field type
455 * @param DateTime $date
456 * @param string $type Field type - date, time, datetime[combo]
458 public function asDbType(DateTime $date, $type)
462 return $this->asDbDate($date);
465 return $this->asDbtime($date);
468 case 'datetimecombo':
470 return $this->asDb($date);
475 * Format DateTime object as user datetime
477 * @param DateTime $date
480 public function asUser(DateTime $date, User $user = null)
482 $this->tzUser($date, $user);
483 return $date->format($this->get_date_time_format($user));
487 * Format date as user-formatted field type
488 * @param DateTime $date
489 * @param string $type Field type - date, time, datetime[combo]
491 public function asUserType(DateTime $date, $type, User $user = null)
495 return $this->asUserDate($date, true, $user);
498 return $this->asUserTime($date, true, $user);
501 case 'datetimecombo':
503 return $this->asUser($date, $user);
508 * Produce timestamp offset by user's timezone
510 * So if somebody converts it to format assuming GMT, it would actually display user's time.
511 * This is used by Javascript.
513 * @param DateTime $date
516 public function asUserTs(DateTime $date, User $user = null)
518 return $date->format('U')+$this->_getUserTZ($user)->getOffset($date);
522 * Format DateTime object as DB date
523 * Note: by default does not convert TZ!
524 * @param DateTime $date
525 * @param boolean $tz Perform TZ conversion?
528 public function asDbDate(DateTime $date, $tz = false)
530 if($tz) $date->setTimezone(self::$gmtTimezone);
531 return $date->format($this->get_db_date_format());
535 * Format DateTime object as user date
536 * Note: by default does not convert TZ!
537 * @param DateTime $date
538 * @param boolean $tz Perform TZ conversion?
541 public function asUserDate(DateTime $date, $tz = false, User $user = null)
543 if($tz) $this->tzUser($date, $user);
544 return $date->format($this->get_date_format($user));
548 * Format DateTime object as DB time
550 * @param DateTime $date
553 public function asDbTime(DateTime $date)
555 $date->setTimezone(self::$gmtTimezone);
556 return $date->format($this->get_db_time_format());
560 * Format DateTime object as user time
562 * @param DateTime $date
565 public function asUserTime(DateTime $date, User $user = null)
567 $this->tzUser($date, $user);
568 return $date->format($this->get_time_format($user));
572 * Get DateTime from DB datetime string
574 * @param string $date
575 * @return SugarDateTime
577 public function fromDb($date)
580 return SugarDateTime::createFromFormat(self::DB_DATETIME_FORMAT, $date, self::$gmtTimezone);
581 } catch (Exception $e) {
582 $GLOBALS['log']->error("fromDb: Conversion of $date from DB format failed: {$e->getMessage()}");
588 * Create a date from a certain type of field in DB format
589 * The types are: date, time, datatime[combo]
590 * @param string $date the datetime string
591 * @param string $type string type
592 * @return SugarDateTime
594 public function fromDbType($date, $type)
598 return $this->fromDbDate($date);
601 return $this->fromDbFormat($date, self::DB_TIME_FORMAT);
604 case 'datetimecombo':
606 return $this->fromDb($date);
611 * Get DateTime from DB date string
613 * @param string $date
614 * @return SugarDateTime
616 public function fromDbDate($date)
619 return SugarDateTime::createFromFormat(self::DB_DATE_FORMAT, $date, self::$gmtTimezone);
620 } catch (Exception $e) {
621 $GLOBALS['log']->error("fromDbDate: Conversion of $date from DB format failed: {$e->getMessage()}");
627 * Get DateTime from DB datetime string using non-standard format
629 * Non-standard format usually would be only date, only time, etc.
631 * @param string $date
632 * @param string $format format to accept
633 * @return SugarDateTime
635 public function fromDbFormat($date, $format)
638 return SugarDateTime::createFromFormat($format, $date, self::$gmtTimezone);
639 } catch (Exception $e) {
640 $GLOBALS['log']->error("fromDbFormat: Conversion of $date from DB format $format failed: {$e->getMessage()}");
646 * Get DateTime from user datetime string
648 * @param string $date
649 * @return SugarDateTime
651 public function fromUser($date, User $user = null)
654 return SugarDateTime::createFromFormat($this->get_date_time_format($user), $date, $this->_getUserTZ($user));
655 } catch (Exception $e) {
656 $uf = $this->get_date_time_format($user);
657 $GLOBALS['log']->error("fromUser: Conversion of $date from user format $uf failed: {$e->getMessage()}");
663 * Create a date from a certain type of field in user format
664 * The types are: date, time, datatime[combo]
665 * @param string $date the datetime string
666 * @param string $type string type
668 * @return SugarDateTime
670 public function fromUserType($date, $type, $user = null)
674 return $this->fromUserDate($date, $user);
677 return $this->fromUserTime($date, $user);
680 case 'datetimecombo':
682 return $this->fromUser($date, $user);
687 * Get DateTime from user time string
689 * @param string $date
690 * @return SugarDateTime
692 public function fromUserTime($date, User $user = null)
695 return SugarDateTime::createFromFormat($this->get_time_format($user), $date, $this->_getUserTZ($user));
696 } catch (Exception $e) {
697 $uf = $this->get_time_format($user);
698 $GLOBALS['log']->error("fromUserTime: Conversion of $date from user format $uf failed: {$e->getMessage()}");
704 * Get DateTime from user date string
705 * Usually for calendar-related functions like holidays
706 * Note: by default does not convert tz!
707 * @param string $date
708 * @param bool $convert_tz perform TZ converson?
710 * @return SugarDateTime
712 public function fromUserDate($date, $convert_tz = false, User $user = null)
715 return SugarDateTime::createFromFormat($this->get_date_format($user), $date, $convert_tz?$this->_getUserTZ($user):self::$gmtTimezone);
716 } catch (Exception $e) {
717 $uf = $this->get_date_format($user);
718 $GLOBALS['log']->error("fromUserDate: Conversion of $date from user format $uf failed: {$e->getMessage()}");
724 * Create a date object from any string
726 * Same formats accepted as for DateTime ctor
728 * @param string $date
730 * @return SugarDateTime
732 public function fromString($date, User $user = null)
735 return new SugarDateTime($date, $this->_getUserTZ($user));
736 } catch (Exception $e) {
737 $GLOBALS['log']->error("fromString: Conversion of $date from string failed: {$e->getMessage()}");
743 * Create DateTime from timestamp
745 * @param interger|string $ts
746 * @return SugarDateTime
748 public function fromTimestamp($ts)
750 return new SugarDateTime("@$ts");
754 * Convert DateTime to GMT timezone
755 * @param DateTime $date
758 public function tzGMT(DateTime $date)
760 return $date->setTimezone(self::$gmtTimezone);
764 * Convert DateTime to user timezone
765 * @param DateTime $date
766 * @param [User] $user
769 public function tzUser(DateTime $date, User $user = null)
771 return $date->setTimezone($this->_getUserTZ($user));
775 * Get string defining midnight in current user's format
776 * @param string $format Time format to use
779 protected function _get_midnight($format = null)
781 $zero = new DateTime("@0", self::$gmtTimezone);
782 return $zero->format($format?$format:$this->get_time_format());
787 * Basic conversion function
789 * @param string $date
790 * @param string $fromFormat
791 * @param DateTimeZone $fromTZ
792 * @param string $toFormat
793 * @param DateTimeZone $toTZ
794 * @param bool $expand
796 protected function _convert($date, $fromFormat, $fromTZ, $toFormat, $toTZ, $expand = false)
803 if ($expand && strlen($date) <= 10) {
804 $date = $this->expandDate($date, $fromFormat);
806 $phpdate = SugarDateTime::createFromFormat($fromFormat, $date, $fromTZ);
807 if ($phpdate == false) {
808 // var_dump($date, $phpdate, $fromFormat, DateTime::getLastErrors() );
809 $GLOBALS['log']->error("convert: Conversion of $date from $fromFormat to $toFormat failed");
812 if ($fromTZ !== $toTZ) {
813 $phpdate->setTimeZone($toTZ);
815 return $phpdate->format($toFormat);
816 } catch (Exception $e) {
817 // var_dump($date, $phpdate, $fromFormat, $fromTZ, DateTime::getLastErrors() );
818 $GLOBALS['log']->error("Conversion of $date from $fromFormat to $toFormat failed: {$e->getMessage()}");
824 * Convert DB datetime to local datetime
826 * TZ conversion is controlled by parameter
828 * @param string $date Original date in DB format
829 * @param bool $meridiem Ignored for BC
830 * @param bool $convert_tz Perform TZ conversion?
831 * @param User $user User owning the conversion formats
832 * @return string Date in display format
834 function to_display_date_time($date, $meridiem = true, $convert_tz = true, $user = null)
836 return $this->_convert($date,
837 self::DB_DATETIME_FORMAT, self::$gmtTimezone, $this->get_date_time_format($user),
838 $convert_tz ? $this->_getUserTZ($user) : self::$gmtTimezone, true);
842 * Converts DB time string to local time string
844 * TZ conversion depends on parameter
846 * @param string $date Time in DB format
847 * @param bool $meridiem
848 * @param bool $convert_tz Perform TZ conversion?
849 * @return string Time in user-defined format
851 public function to_display_time($date, $meridiem = true, $convert_tz = true)
853 if($convert_tz && strpos($date, ' ') === false) {
854 // we need TZ adjustment but have no date, assume today
855 $date = $this->expandTime($date, self::DB_DATETIME_FORMAT, self::$gmtTimezone);
857 return $this->_convert($date,
858 $convert_tz ? self::DB_DATETIME_FORMAT : self::DB_TIME_FORMAT, self::$gmtTimezone,
859 $this->get_time_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone);
863 * Splits time in given format into components
865 * Components: h, m, s, a (am/pm) if format requires it
866 * If format has am/pm, hour is 12-based, otherwise 24-based
868 * @param string $date
869 * @param string $format
872 public function splitTime($date, $format)
874 if (! ($date instanceof DateTime)) {
875 $date = SugarDateTime::createFromFormat($format, $date);
877 $ampm = strpbrk($format, 'aA');
879 "h" => ($ampm == false) ? $date->format("H") : $date->format("h"),
880 'm' => $date->format("i"),
881 's' => $date->format("s")
884 $datearr['a'] = ($ampm{0} == 'a') ? $date->format("a") : $date->format("A");
890 * Converts DB date string to local date string
892 * TZ conversion depens on parameter
894 * @param string $date Date in DB format
895 * @param bool $convert_tz Perform TZ conversion?
896 * @return string Date in user-defined format
898 public function to_display_date($date, $convert_tz = true)
900 return $this->_convert($date,
901 self::DB_DATETIME_FORMAT, self::$gmtTimezone,
902 $this->get_date_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone, true);
906 * Convert date from format to format
908 * No TZ conversion is performed!
910 * @param string $date
911 * @param string $fromformat Source format
912 * @param string $toformat Target format
913 * @return string Converted date
915 function to_display($date, $from, $to)
917 return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
921 * Get DB datetime format
924 public function get_db_date_time_format()
926 return self::DB_DATETIME_FORMAT;
933 public function get_db_date_format()
935 return self::DB_DATE_FORMAT;
942 public function get_db_time_format()
944 return self::DB_TIME_FORMAT;
948 * Convert date from local datetime to GMT-based DB datetime
950 * Includes TZ conversion.
952 * @param string $date
953 * @return string Datetime in DB format
955 public function to_db($date)
957 return $this->_convert($date,
958 $this->get_date_time_format(), $this->_getUserTZ(),
959 $this->get_db_date_time_format(), self::$gmtTimezone,
964 * Convert local datetime to DB date
966 * TZ conversion depends on parameter. If false, only format conversion is performed.
968 * @param string $date Local date
969 * @param bool $convert_tz Should time and TZ be taken into account?
970 * @return string Date in DB format
972 public function to_db_date($date, $convert_tz = true)
974 return $this->_convert($date,
975 $this->get_date_time_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone,
976 self::DB_DATE_FORMAT, self::$gmtTimezone, true);
980 * Convert local datetime to DB time
982 * TZ conversion depends on parameter. If false, only format conversion is performed.
984 * @param string $date Local date
985 * @param bool $convert_tz Should time and TZ be taken into account?
986 * @return string Time in DB format
988 public function to_db_time($date, $convert_tz = true)
990 $format = $this->get_date_time_format();
991 $tz = $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone;
992 if($convert_tz && strpos($date, ' ') === false) {
993 // we need TZ adjustment but have short string, expand it to full one
994 // FIXME: if the string is short, should we assume date or time?
995 $date = $this->expandTime($date, $format, $tz);
997 return $this->_convert($date,
998 $convert_tz ? $format : $this->get_time_format(),
1000 self::DB_TIME_FORMAT, self::$gmtTimezone);
1004 * Takes a Date & Time value in local format and converts them to DB format
1007 * @param string $date
1008 * @param string $time
1009 * @return array Date & time in DB format
1011 public function to_db_date_time($date, $time)
1014 $phpdate = SugarDateTime::createFromFormat($this->get_date_time_format(),
1015 $this->merge_date_time($date, $time), self::$gmtTimezone);
1016 if ($phpdate == false) {
1017 return array('', '');
1019 return array($this->asDbDate($phpdate), $this->asDbTime($phpdate));
1020 } catch (Exception $e) {
1021 $GLOBALS['log']->error("Conversion of $date,$time failed");
1022 return array('', '');
1027 * Return current time in DB format
1030 public function nowDb()
1032 if(!$this->allow_cache) {
1033 $nowGMT = $this->getNow();
1035 $nowGMT = $this->now;
1037 return $this->asDb($nowGMT);
1041 * Return current date in DB format
1044 public function nowDbDate()
1046 if(!$this->allow_cache) {
1047 $nowGMT = $this->getNow();
1049 $nowGMT = $this->now;
1051 return $this->asDbDate($nowGMT, true);
1055 * Get 'now' DateTime object
1056 * @param bool $userTz return in user timezone?
1057 * @return SugarDateTime
1059 public function getNow($userTz = false)
1061 if(!$this->allow_cache) {
1062 return new SugarDateTime("now", $userTz?$this->_getUserTz():self::$gmtTimezone);
1064 // TODO: should we return clone?
1065 $now = clone $this->now;
1067 return $this->tzUser($now);
1074 * For testability - predictable time value
1075 * @param DateTime $now
1077 public function setNow($now)
1084 * Return current datetime in local format
1087 public function now()
1089 return $this->asUser($this->getNow());
1093 * Return current date in User format
1096 public function nowDate()
1098 return $this->asUserDate($this->getNow());
1102 * Get user format's time separator
1105 public function timeSeparator()
1107 if (empty($this->time_separator)) {
1108 $this->time_separator = $this->timeSeparatorFormat($this->get_time_format());
1110 return $this->time_separator;
1114 * Find out format's time separator
1115 * @param string $timeformat Time format
1117 public function timeSeparatorFormat($timeformat)
1119 $date = $this->_convert("00:11:22", self::DB_TIME_FORMAT, null, $timeformat, null);
1120 if (preg_match('/\d+(.+?)11/', $date, $matches)) {
1129 * Returns start and end of a certain local date in GMT
1130 * Example: for May 19 in PDT start would be 2010-05-19 07:00:00, end would be 2010-05-20 06:59:59
1131 * @param string|DateTime $date Date in any suitable format
1132 * @return array Start & end date in start, startdate, starttime, end, enddate, endtime
1134 public function getDayStartEndGMT($date, User $user = null)
1136 if ($date instanceof DateTime) {
1138 $min->setTimezone($this->_getUserTZ($user));
1140 $max->setTimezone($this->_getUserTZ($user));
1142 $min = new DateTime($date, $this->_getUserTZ($user));
1143 $max = new DateTime($date, $this->_getUserTZ($user));
1145 $min->setTime(0, 0);
1146 $max->setTime(23, 59, 59);
1148 $min->setTimezone(self::$gmtTimezone);
1149 $max->setTimezone(self::$gmtTimezone);
1151 $result['start'] = $this->asDb($min);
1152 $result['startdate'] = $this->asDbDate($min);
1153 $result['starttime'] = $this->asDbTime($min);
1154 $result['end'] = $this->asDb($max);
1155 $result['enddate'] = $this->asDbDate($max);
1156 $result['endtime'] = $this->asDbtime($max);
1162 * Expand date format by adding midnight to it
1163 * Note: date is assumed to be in target format already
1164 * @param string $date
1165 * @param string $format Target format
1168 public function expandDate($date, $format)
1170 $formats = $this->split_date_time($format);
1171 if(isset($formats[1])) {
1172 return $this->merge_date_time($date, $this->_get_midnight($formats[1]));
1178 * Expand time format by adding today to it
1179 * Note: time is assumed to be in target format already
1180 * @param string $date
1181 * @param string $format Target format
1182 * @param DateTimeZone $tz
1184 public function expandTime($date, $format, $tz)
1186 $formats = $this->split_date_time($format);
1187 if(isset($formats[1])) {
1188 $now = clone $this->getNow();
1189 $now->setTimezone($tz);
1190 return $this->merge_date_time($now->format($formats[0]), $date);
1196 * Get midnight (start of the day) in local time format
1198 * @return Time string
1200 function get_default_midnight()
1202 return $this->_get_midnight($this->get_time_format());
1206 * Get the name of the timezone for the user
1207 * @param User $user User, default - current user
1210 public static function userTimezone(User $user = null)
1212 $user = self::getInstance()->_getUser($user);
1216 $tz = self::getInstance()->_getUserTZ($user);
1218 return $tz->getName();
1224 * Guess the timezone for the current user
1227 public static function guessTimezone($userOffset = 0)
1229 if(!is_numeric($userOffset)) {
1232 $defaultZones= array(
1233 'America/Anchorage', 'America/Los_Angeles', 'America/Phoenix', 'America/Chicago',
1234 'America/New_York', 'America/Argentina/Buenos_Aires', 'America/Montevideo',
1235 'Europe/London', 'Europe/Amsterdam', 'Europe/Athens', 'Europe/Moscow',
1236 'Asia/Tbilisi', 'Asia/Omsk', 'Asia/Jakarta', 'Asia/Hong_Kong',
1237 'Asia/Tokyo', 'Pacific/Guam', 'Australia/Sydney', 'Australia/Perth',
1240 $now = new DateTime();
1241 $tzlist = timezone_identifiers_list();
1242 if($userOffset == 0) {
1243 $gmtOffset = date('Z');
1245 if(in_array($nowtz, $tzlist)) {
1246 array_unshift($defaultZones, $nowtz);
1248 $nowtz = timezone_name_from_abbr(date('T'), $gmtOffset, date('I'));
1249 if(in_array($nowtz, $tzlist)) {
1250 array_unshift($defaultZones, $nowtz);
1254 $gmtOffset = $userOffset * 60;
1256 foreach($defaultZones as $zoneName) {
1257 $tz = new DateTimeZone($zoneName);
1258 if($tz->getOffset($now) == $gmtOffset) {
1259 return $tz->getName();
1263 foreach($tzlist as $zoneName) {
1264 $tz = new DateTimeZone($zoneName);
1265 if($tz->getOffset($now) == $gmtOffset) {
1266 return $tz->getName();
1273 * Get the description of the user timezone for specific date
1275 * We need the date because it can be DST or non-DST
1276 * Note it's different from TZ name in tzName() that relates to current date
1277 * @param User $user User, default - current user
1280 public static function userTimezoneSuffix(DateTime $date, User $user = null)
1282 $user = self::getInstance()->_getUser($user);
1286 self::getInstance()->tzUser($date, $user);
1287 return $date->format('T(P)');
1291 * Get display name for a certain timezone
1292 * Note: it uses current date for GMT offset, so it may be not suitable for displaying generic dates
1293 * @param string|DateTimeZone $name TZ name
1296 public static function tzName($name)
1301 if($name instanceof DateTimeZone) {
1304 $tz = timezone_open($name);
1309 $now = new DateTime("now", $tz);
1310 $off = $now->getOffset();
1311 $translated = translate('timezone_dom','',$name);
1312 if(is_string($translated) && !empty($translated)) {
1313 $name = $translated;
1315 return sprintf("%s (GMT%+2d:%02d)%s", str_replace('_',' ', $name), $off/3600, (abs($off)/60)%60, "");//$now->format('I')==1?"(+DST)":"");
1319 public static function _sortTz($a, $b)
1321 if($a[0] == $b[0]) {
1322 return strcmp($a[1], $b[1]);
1324 return $a[0]<$b[0]?-1:1;
1329 * Get list of all timezones in the system
1332 public static function getTimezoneList()
1334 $now = new DateTime();
1335 $res_zones = $zones = array();
1336 foreach(timezone_identifiers_list() as $zoneName) {
1337 $tz = new DateTimeZone($zoneName);
1338 $zones[$zoneName] = array($tz->getOffset($now), self::tzName($zoneName));
1340 uasort($zones, array('TimeDate', '_sortTz'));
1341 foreach($zones as $name => $zonedef) {
1342 $res_zones[$name] = $zonedef[1];
1348 * Print timestamp in RFC2616 format:
1351 public static function httpTime($ts = null)
1356 return gmdate(self::RFC2616_FORMAT, $ts);
1360 * Create datetime object from calendar array
1361 * @param array $time
1362 * @return SugarDateTime
1364 public function fromTimeArray($time)
1366 if (! isset( $time) || count($time) == 0 )
1368 return $this->nowDb();
1370 elseif ( isset( $time['ts']))
1372 return $this->fromTimestamp($time['ts']);
1374 elseif ( isset( $time['date_str']))
1376 return $this->fromDb($time['date_str']);
1383 $now = $this->getNow(true);
1385 $month = $now->month;
1387 if (isset($time['sec']))
1389 $sec = $time['sec'];
1391 if (isset($time['min']))
1393 $min = $time['min'];
1395 if (isset($time['hour']))
1397 $hour = $time['hour'];
1399 if (isset($time['day']))
1401 $day = $time['day'];
1403 if (isset($time['month']))
1405 $month = $time['month'];
1407 if (isset($time['year']) && $time['year'] >= 1970)
1409 $year = $time['year'];
1411 return $now->setDate($year, $month, $day)->setTime($hour, $min, $sec)->setTimeZone(self::$gmtTimezone);
1417 * Returns the date portion of a datetime string
1419 * @param string $datetime
1422 public function getDatePart($datetime)
1424 list($date, $time) = $this->split_date_time($datetime);
1429 * Returns the time portion of a datetime string
1431 * @param string $datetime
1434 public function getTimePart($datetime)
1436 list($date, $time) = $this->split_date_time($datetime);
1441 * Returns the offset from user's timezone to GMT
1443 * @param DateTime $time When the offset is taken, default is now
1444 * @return int Offset in minutes
1446 public function getUserUTCOffset(User $user = null, DateTime $time = null)
1451 return $this->_getUserTZ($user)->getOffset($time) / 60;
1455 * Create regexp from datetime format
1456 * @param string $format
1457 * @return string Regular expression string
1459 public static function get_regular_expression($format)
1462 $regPositions = array();
1463 $ignoreNextChar = false;
1465 foreach (str_split($format) as $char) {
1466 if (! $ignoreNextChar && isset(self::$format_to_regexp[$char])) {
1467 $newFormat .= '(' . self::$format_to_regexp[$char] . ')';
1468 $regPositions[$char] = $count;
1471 $ignoreNextChar = false;
1472 $newFormat .= $char;
1475 if ($char == "\\") {
1476 $ignoreNextChar = true;
1480 return array('format' => $newFormat, 'positions' => $regPositions);
1483 // format - date expression ('' means now) for start and end of the range
1484 protected $date_expressions = array(
1485 'yesterday' => array("-1 day", "-1 day"),
1486 'today' => array("", ""),
1487 'tomorrow' => array("+1 day", "+1 day"),
1488 'last_7_days' => array("-6 days", ""),
1489 'next_7_days' => array("", "+6 days"),
1490 'last_30_days' => array("-29 days", ""),
1491 'next_30_days' => array("", "+29 days"),
1495 * Parse date template
1496 * @param string $template Date expression
1497 * @param bool $daystart Do we want start or end of the day?
1500 protected function parseFromTemplate($template, $daystart, User $user = null)
1502 $now = $this->tzUser($this->getNow(), $user);
1503 if(!empty($template[0])) {
1504 $now->modify($template[0]);
1507 return $now->get_day_begin();
1509 return $now->get_day_end();
1514 * Get month-long range mdiff months from now
1516 protected function diffMon($mdiff, User $user)
1518 $now = $this->tzUser($this->getNow(), $user);
1519 $now->setDate($now->year, $now->month+$mdiff, 1);
1520 $start = $now->get_day_begin();
1521 $end = $now->setDate($now->year, $now->month, $now->days_in_month)->setTime(23, 59, 59);
1522 return array($start, $end);
1526 * Get year-long range ydiff years from now
1528 protected function diffYear($ydiff, User $user)
1530 $now = $this->tzUser($this->getNow(), $user);
1531 $now->setDate($now->year+$ydiff, 1, 1);
1532 $start = $now->get_day_begin();
1533 $end = $now->setDate($now->year, 12, 31)->setTime(23, 59, 59);
1534 return array($start, $end);
1538 * Parse date range expression
1539 * Returns beginning and end of the range as a date
1540 * @param string $range
1544 public function parseDateRange($range, User $user = null)
1546 if(isset($this->date_expressions[$range])) {
1547 return array($this->parseFromTemplate($this->date_expressions[$range][0], true, $user),
1548 $this->parseFromTemplate($this->date_expressions[$range][1], false, $user)
1553 return $this->diffMon(1, $user);
1555 return $this->diffMon(-1, $user);
1557 return $this->diffMon(0, $user);
1559 return $this->diffYear(-1, $user);
1561 return $this->diffYear(0, $user);
1563 return $this->diffYear(1, $user);
1569 /********************* OLD functions, should not be used publicly anymore ****************/
1571 * Merge time without am/pm with am/pm string
1572 * @TODO find better way to do this!
1573 * @deprecated for public use
1574 * @param string $date
1575 * @param string $format User time format
1576 * @param string $mer
1579 function merge_time_meridiem($date, $format, $mer)
1581 $date = trim($date);
1585 $fakeMerFormat = str_replace(array('a', 'A'), array('@~@', '@~@'), $format);
1586 $noMerFormat = str_replace(array('a', 'A'), array('', ''), $format);
1587 $newDate = $this->swap_formats($date, $noMerFormat, $fakeMerFormat);
1588 return str_replace('@~@', $mer, $newDate);
1592 * @deprecated for public use
1593 * Convert date from one format to another
1595 * @param string $date
1596 * @param string $from
1600 public function swap_formats($date, $from, $to)
1602 return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
1606 * @deprecated for public use
1607 * handles offset values for Timezones and DST
1608 * @param $date string date/time formatted in user's selected format
1609 * @param $format string destination format value as passed to PHP's date() funtion
1610 * @param $to boolean
1611 * @param $user object user object from which Timezone and DST
1612 * @param $usetimezone string timezone name
1613 * values will be derived
1614 * @return string date formatted and adjusted for TZ and DST
1616 function handle_offset($date, $format, $to = true, $user = null, $usetimezone = null)
1618 $tz = empty($usetimezone)?$this->_getUserTZ($user):new DateTimeZone($usetimezone);
1619 $dateobj = new SugarDateTime($date, $to? self::$gmtTimezone : $tz);
1620 $dateobj->setTimezone($to ? $tz: self::$gmtTimezone);
1621 return $dateobj->format($format);
1622 // return $this->_convert($date, $format, $to ? self::$gmtTimezone : $tz, $format, $to ? $tz : self::$gmtTimezone);
1626 * @deprecated for public use
1627 * Get current GMT datetime in DB format
1630 function get_gmt_db_datetime()
1632 return $this->nowDb();
1636 * @deprecated for public use
1637 * Get current GMT date in DB format
1640 function get_gmt_db_date()
1642 return $this->nowDbDate();
1646 * @deprecated for public use
1647 * this method will take an input $date variable (expecting Y-m-d format)
1648 * and get the GMT equivalent - with an hour-level granularity :
1649 * return the max value of a given locale's
1650 * date+time in GMT metrics (i.e., if in PDT, "2005-01-01 23:59:59" would be
1651 * "2005-01-02 06:59:59" in GMT metrics)
1653 function handleOffsetMax($date)
1655 $min = new DateTime($date, $this->_getUserTZ());
1656 $min->setTime(0, 0);
1657 $max = new DateTime($date, $this->_getUserTZ());
1658 $max->setTime(23, 59, 59);
1660 $min->setTimezone(self::$gmtTimezone);
1661 $max->setTimezone(self::$gmtTimezone);
1663 $gmtDateTime['date'] = $this->asDbDate($max, false);
1664 $gmtDateTime['time'] = $this->asDbDate($max, false);
1665 $gmtDateTime['min'] = $this->asDb($min);
1666 $gmtDateTime['max'] = $this->asDb($max);
1668 return $gmtDateTime;
1672 * @deprecated for public use
1673 * this returns the adjustment for a user against the server time
1675 * @return integer number of minutes to adjust a time by to get the appropriate time for the user
1677 public function adjustmentForUserTimeZone()
1679 $tz = $this->_getUserTZ();
1680 $server_tz = new DateTimeZone(date_default_timezone_get());
1681 if ($tz && $server_tz) {
1682 return ($server_tz->getOffset($this->now) - $tz->getOffset($this->now)) / 60;
1688 * @deprecated for public use
1689 * assumes that olddatetime is in Y-m-d H:i:s format
1691 function convert_to_gmt_datetime($olddatetime)
1693 if (! empty($olddatetime)) {
1694 return date('Y-m-d H:i:s', strtotime($olddatetime) - date('Z'));
1699 * @deprecated for public use
1700 * get user timezone info
1702 public function getUserTimeZone(User $user = null)
1704 $tz = $this->_getUserTZ($user);
1705 return array("gmtOffset" => $tz->getOffset($this->now) / 60);
1709 * @deprecated for public use
1710 * get timezone start & end
1712 public function getDSTRange($year, $zone)
1714 $year = SugarDateTime::createFromFormat("Y", $year, self::$gmtTimezone);
1715 $year_end = clone $year;
1716 $year_end->setDate((int) $year, 12, 31);
1717 $year_end->setTime(23, 59, 59);
1718 $year->setDate((int) $year, 1, 1);
1719 $year->setTime(0, 0, 0);
1720 $tz = $this->_getUserTZ();
1721 $transitions = $tz->getTransitions($year->getTimestamp(), $year_end->getTimestamp());
1723 while (! $transitions[$idx]["isdst"])
1725 $startdate = new DateTime("@" . $transitions[$idx]["ts"], self::$gmtTimezone);
1726 while ($transitions[$idx]["isdst"])
1728 $enddate = new DateTime("@" . $transitions[$idx]["ts"], self::$gmtTimezone);
1729 return array("start" => $this->asDb($startdate), "end" => $this->asDb($enddate));
1732 /****************** GUI stuff that really shouldn't be here, will be moved ************/
1734 * Get Javascript variables setup for user date format validation
1735 * @deprecated moved to SugarView
1736 * @return string JS code
1738 function get_javascript_validation()
1740 return SugarView::getJavascriptValidation();
1745 * This method renders a <select> HTML form element based on the
1746 * user's time format preferences, with give date's value highlighted.
1748 * If user's prefs have no AM/PM string, returns empty string.
1750 * @todo There is hardcoded HTML in here that does not allow for localization
1751 * of the AM/PM am/pm Strings in this drop down menu. Also, perhaps
1752 * change to the substr_count function calls to strpos
1753 * TODO: Remove after full switch to fields
1755 * @param string $prefix Prefix for SELECT
1756 * @param string $date Date in display format
1757 * @param string $attrs Additional attributes for SELECT
1758 * @return string SELECT HTML
1760 function AMPMMenu($prefix, $date, $attrs = '')
1762 $tf = $this->get_time_format();
1763 $am = strpbrk($tf, 'aA');
1767 $selected = array("am" => "", "pm" => "", "AM" => "", "PM" => "");
1768 if (preg_match('/([ap]m)/i', $date, $match)) {
1769 $selected[$match[1]] = " selected";
1772 $menu = "<select name='" . $prefix . "meridiem' " . $attrs . ">";
1773 if ($am{0} == 'a') {
1774 $menu .= "<option value='am'{$selected["am"]}>am";
1775 $menu .= "<option value='pm'{$selected["pm"]}>pm";
1777 $menu .= "<option value='AM'{$selected["AM"]}>AM";
1778 $menu .= "<option value='PM'{$selected["PM"]}>PM";
1781 return $menu . '</select>';
1785 * TODO: Remove after full switch to fields
1787 function get_user_date_format()
1789 return str_replace(array('Y', 'm', 'd'), array('yyyy', 'mm', 'dd'), $this->get_date_format());
1793 * TODO: Remove after full switch to fields
1797 function get_user_time_format()
1799 global $sugar_config;
1800 $time_pref = $this->get_time_format();
1802 if (! empty($time_pref) && ! empty($sugar_config['time_formats'][$time_pref])) {
1803 return $sugar_config['time_formats'][$time_pref];
1806 return '23:00'; //default