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-2013 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';
41 * 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 const SECONDS_IN_A_DAY = 86400;
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;
64 * Regexp for matching format elements
67 protected static $format_to_regexp = array(
86 * Relation between date() and strftime() formats
89 public static $format_to_str = array(
114 * GMT timezone object
118 protected static $gmtTimezone;
138 protected $current_user_id;
143 protected $current_user_tz;
146 * Separator for current user time format
150 protected $time_separator;
153 * Always consider user TZ to be GMT and date format DB format - for SOAP etc.
157 protected $always_db = false;
160 * Global instance of TimeDate
163 protected static $timedate;
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.
172 public $allow_cache = true;
175 * Create TimeDate handler
176 * @param User $user User to work with, default if current user
178 public function __construct(User $user = null)
180 if (self::$gmtTimezone == null) {
181 self::$gmtTimezone = new DateTimeZone("UTC");
183 $this->now = new SugarDateTime();
184 $this->tzGMT($this->now);
189 * Set flag specifying we should always use DB format
193 public function setAlwaysDb($flag = true)
195 $this->always_db = $flag;
201 * Get "always use DB format" flag
204 public function isAlwaysDb()
206 return !empty($GLOBALS['disable_date_format']) || $this->always_db;
210 * Get TimeDate instance
213 public static function getInstance()
215 if(empty(self::$timedate)) {
216 if(ini_get('date.timezone') == '') {
217 // Remove warning about default timezone
218 date_default_timezone_set(@date('e'));
220 $tz = self::guessTimezone();
221 } catch(Exception $e) {
222 $tz = "UTC"; // guess failed, switch to UTC
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!");
227 date_default_timezone_set($tz);
229 self::$timedate = new self;
231 return self::$timedate;
235 * Set current user for this object
237 * @param User $user User object, default is current user
240 public function setUser(User $user = null)
248 * Figure out what the required user is
250 * The order is: supplied parameter, TimeDate's user, global current user
252 * @param User $user User object, default is current user
256 protected function _getUser(User $user = null)
262 $user = $GLOBALS['current_user'];
268 * Get timezone for the specified user
270 * @param User $user User object, default is current user
271 * @return DateTimeZone
273 protected function _getUserTZ(User $user = null)
275 $user = $this->_getUser($user);
276 if (empty($user) || $this->isAlwaysDb()) {
277 return self::$gmtTimezone;
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;
285 $usertimezone = $user->getPreference('timezone');
286 if(empty($usertimezone)) {
287 return self::$gmtTimezone;
290 $tz = new DateTimeZone($usertimezone);
291 } catch (Exception $e) {
292 $GLOBALS['log']->fatal('Unknown timezone: ' . $usertimezone);
293 return self::$gmtTimezone;
296 if (empty($this->current_user_id)) {
297 $this->current_user_id = $user->id;
298 $this->current_user_tz = $tz;
305 * Clears all cached data regarding current user
307 public function clearCache()
309 $this->current_user_id = null;
310 $this->current_user_tz = null;
311 $this->time_separator = null;
312 $this->now = new SugarDateTime();
316 * Get user date format.
319 * @param User $user user object, current user if not specified
322 public function get_date_format(User $user = null)
324 $user = $this->_getUser($user);
326 if (empty($user) || $this->isAlwaysDb()) {
327 return self::DB_DATE_FORMAT;
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');
336 $datef = $GLOBALS['sugar_config']['default_date_format'];
346 * Get user time format.
349 * @param User $user user object, current user if not specified
352 public function get_time_format(/*User*/ $user = null)
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);
363 $user = $this->_getUser($user);
365 if (empty($user) || $this->isAlwaysDb()) {
366 return self::DB_TIME_FORMAT;
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');
375 $timef = $GLOBALS['sugar_config']['default_time_format'];
384 * Get user datetime format.
386 * @param User $user user object, current user if not specified
389 public function get_date_time_format($user = null)
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)) {
403 $cacheKey= $this->get_date_time_format_cache_key($user);
404 $cachedValue = sugar_cache_retrieve($cacheKey);
406 if(!empty($cachedValue) )
412 $value = $this->merge_date_time($this->get_date_format($user), $this->get_time_format($user));
413 sugar_cache_put($cacheKey,$value,0);
419 * Retrieve the cache key used for user date/time formats
424 public function get_date_time_format_cache_key($user)
426 $cacheKey = get_class($this) ."dateTimeFormat";
427 $user = $this->_getUser($user);
429 if($user instanceof User)
431 $cacheKey .= "_{$user->id}";
434 if( $this->isAlwaysDb() )
435 $cacheKey .= '_asdb';
441 * Get user's first day of week setting.
443 * @param User $user user object, current user if not specified
444 * @return int Day, 0 = Sunday, 1 = Monday, etc...
446 public function get_first_day_of_week(User $user = null)
448 $user = $this->_getUser($user);
453 $fdow = $user->getPreference('fdow');
463 * Make one datetime string from date string and time string
465 * @param string $date
466 * @param string $time
467 * @return string New datetime string
469 function merge_date_time($date, $time)
471 return $date . ' ' . $time;
475 * Split datetime string into date & time
477 * @param string $datetime
480 function split_date_time($datetime)
482 return explode(' ', $datetime, 2);
487 * Get user date format in Javascript form
490 function get_cal_date_format()
492 return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_format());
496 * Get user time format in Javascript form
499 function get_cal_time_format()
501 return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_time_format());
505 * Get user date&time format in Javascript form
508 function get_cal_date_time_format()
510 return str_replace(array_keys(self::$format_to_str), array_values(self::$format_to_str), $this->get_date_time_format());
514 * Verify if the date string conforms to a format
516 * @param string $date
517 * @param string $format Format to check
520 * @return bool Is the date ok?
522 public function check_matching_format($date, $format)
525 $dt = SugarDateTime::createFromFormat($format, $date);
526 if (!is_object($dt)) {
529 } catch (Exception $e) {
536 * Format DateTime object as DB datetime
538 * @param DateTime $date
541 public function asDb(DateTime $date)
543 $date->setTimezone(self::$gmtTimezone);
544 return $date->format($this->get_db_date_time_format());
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
553 public function asDbType(DateTime $date, $type)
557 return $this->asDbDate($date);
560 return $this->asDbtime($date);
563 case 'datetimecombo':
565 return $this->asDb($date);
570 * Format DateTime object as user datetime
572 * @param DateTime $date
576 public function asUser(DateTime $date, User $user = null)
578 $this->tzUser($date, $user);
579 return $date->format($this->get_date_time_format($user));
583 * Format date as user-formatted field type
584 * @param DateTime $date
585 * @param string $type Field type - date, time, datetime[combo]
589 public function asUserType(DateTime $date, $type, User $user = null)
593 return $this->asUserDate($date, true, $user);
596 return $this->asUserTime($date, true, $user);
599 case 'datetimecombo':
601 return $this->asUser($date, $user);
606 * Produce timestamp offset by user's timezone
608 * So if somebody converts it to format assuming GMT, it would actually display user's time.
609 * This is used by Javascript.
611 * @param DateTime $date
615 public function asUserTs(DateTime $date, User $user = null)
617 return $date->format('U')+$this->_getUserTZ($user)->getOffset($date);
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?
627 public function asDbDate(DateTime $date, $tz = false)
629 if($tz) $date->setTimezone(self::$gmtTimezone);
630 return $date->format($this->get_db_date_format());
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?
641 public function asUserDate(DateTime $date, $tz = false, User $user = null)
643 if($tz) $this->tzUser($date, $user);
644 return $date->format($this->get_date_format($user));
648 * Format DateTime object as DB time
650 * @param DateTime $date
653 public function asDbTime(DateTime $date)
655 $date->setTimezone(self::$gmtTimezone);
656 return $date->format($this->get_db_time_format());
660 * Format DateTime object as user time
662 * @param DateTime $date
666 public function asUserTime(DateTime $date, User $user = null)
668 $this->tzUser($date, $user);
669 return $date->format($this->get_time_format($user));
673 * Get DateTime from DB datetime string
675 * @param string $date
676 * @return SugarDateTime
678 public function fromDb($date)
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()}");
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
695 public function fromDbType($date, $type)
699 return $this->fromDbDate($date);
702 return $this->fromDbFormat($date, self::DB_TIME_FORMAT);
705 case 'datetimecombo':
707 return $this->fromDb($date);
712 * Get DateTime from DB date string
714 * @param string $date
715 * @return SugarDateTime
717 public function fromDbDate($date)
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()}");
728 * Get DateTime from DB datetime string using non-standard format
730 * Non-standard format usually would be only date, only time, etc.
732 * @param string $date
733 * @param string $format format to accept
734 * @return SugarDateTime
736 public function fromDbFormat($date, $format)
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()}");
747 * Get DateTime from user datetime string
749 * @param string $date
751 * @return SugarDateTime
753 public function fromUser($date, User $user = null)
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()}");
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");
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
775 * @return SugarDateTime
777 public function fromUserType($date, $type, $user = null)
781 return $this->fromUserDate($date, $user);
784 return $this->fromUserTime($date, $user);
787 case 'datetimecombo':
789 return $this->fromUser($date, $user);
794 * Get DateTime from user time string
796 * @param string $date
798 * @return SugarDateTime
800 public function fromUserTime($date, User $user = null)
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()}");
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?
818 * @return SugarDateTime
820 public function fromUserDate($date, $convert_tz = false, User $user = null)
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()}");
832 * Create a date object from any string
834 * Same formats accepted as for DateTime ctor
836 * @param string $date
838 * @return SugarDateTime
840 public function fromString($date, User $user = null)
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()}");
851 * Create DateTime from timestamp
853 * @param interger|string $ts
854 * @return SugarDateTime
856 public function fromTimestamp($ts)
858 return new SugarDateTime("@$ts");
862 * Convert DateTime to GMT timezone
863 * @param DateTime $date
866 public function tzGMT(DateTime $date)
868 return $date->setTimezone(self::$gmtTimezone);
872 * Convert DateTime to user timezone
873 * @param DateTime $date
877 public function tzUser(DateTime $date, User $user = null)
879 return $date->setTimezone($this->_getUserTZ($user));
883 * Get string defining midnight in current user's format
884 * @param string $format Time format to use
887 protected function _get_midnight($format = null)
889 $zero = new DateTime("@0", self::$gmtTimezone);
890 return $zero->format($format?$format:$this->get_time_format());
895 * Basic conversion function
897 * Converts between two string dates in different formats and timezones
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
907 protected function _convert($date, $fromFormat, $fromTZ, $toFormat, $toTZ, $expand = false)
914 if ($expand && strlen($date) <= 10) {
915 $date = $this->expandDate($date, $fromFormat);
917 $phpdate = SugarDateTime::createFromFormat($fromFormat, $date, $fromTZ);
918 if ($phpdate == false) {
919 $GLOBALS['log']->error("convert: Conversion of $date from $fromFormat to $toFormat failed");
922 if ($fromTZ !== $toTZ && $toTZ != null) {
923 $phpdate->setTimeZone($toTZ);
925 return $phpdate->format($toFormat);
926 } catch (Exception $e) {
927 $GLOBALS['log']->error("Conversion of $date from $fromFormat to $toFormat failed: {$e->getMessage()}");
933 * Convert DB datetime to local datetime
935 * TZ conversion is controlled by parameter
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
943 function to_display_date_time($date, $meridiem = true, $convert_tz = true, $user = null)
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);
950 * Converts DB time string to local time string
952 * TZ conversion depends on parameter
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
959 public function to_display_time($date, $meridiem = true, $convert_tz = true)
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);
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);
971 * Splits time in given format into components
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
976 * @param string $date
977 * @param string $format
980 public function splitTime($date, $format)
982 if (! ($date instanceof DateTime)) {
983 $date = SugarDateTime::createFromFormat($format, $date);
985 $ampm = strpbrk($format, 'aA');
987 "h" => ($ampm == false) ? $date->format("H") : $date->format("h"),
988 'm' => $date->format("i"),
989 's' => $date->format("s")
992 $datearr['a'] = ($ampm{0} == 'a') ? $date->format("a") : $date->format("A");
998 * Converts DB date string to local date string
1000 * TZ conversion depens on parameter
1002 * @param string $date Date in DB format
1003 * @param bool $convert_tz Perform TZ conversion?
1004 * @return string Date in user-defined format
1006 public function to_display_date($date, $convert_tz = true)
1008 return $this->_convert($date,
1009 self::DB_DATETIME_FORMAT, self::$gmtTimezone,
1010 $this->get_date_format(), $convert_tz ? $this->_getUserTZ() : self::$gmtTimezone, true);
1014 * Convert date from format to format
1016 * No TZ conversion is performed!
1018 * @param string $date
1019 * @param string $from Source format
1020 * @param string $to Destination format
1021 * @return string Converted date
1023 function to_display($date, $from, $to)
1025 return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
1029 * Get DB datetime format
1032 public function get_db_date_time_format()
1034 return self::DB_DATETIME_FORMAT;
1038 * Get DB date format
1041 public function get_db_date_format()
1043 return self::DB_DATE_FORMAT;
1047 * Get DB time format
1050 public function get_db_time_format()
1052 return self::DB_TIME_FORMAT;
1056 * Convert date from local datetime to GMT-based DB datetime
1058 * Includes TZ conversion.
1060 * @param string $date
1061 * @return string Datetime in DB format
1063 public function to_db($date)
1065 return $this->_convert($date,
1066 $this->get_date_time_format(), $this->_getUserTZ(),
1067 $this->get_db_date_time_format(), self::$gmtTimezone,
1072 * Convert local datetime to DB date
1074 * TZ conversion depends on parameter. If false, only format conversion is performed.
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
1080 public function to_db_date($date, $convert_tz = true)
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);
1088 * Convert local datetime to DB time
1090 * TZ conversion depends on parameter. If false, only format conversion is performed.
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
1096 public function to_db_time($date, $convert_tz = true)
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);
1105 return $this->_convert($date,
1106 $convert_tz ? $format : $this->get_time_format(),
1108 self::DB_TIME_FORMAT, self::$gmtTimezone);
1112 * Takes a Date & Time value in local format and converts them to DB format
1115 * @param string $date
1116 * @param string $time
1117 * @return array Date & time in DB format
1119 public function to_db_date_time($date, $time)
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('', '');
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('', '');
1135 * Return current time in DB format
1138 public function nowDb()
1140 if(!$this->allow_cache) {
1141 $nowGMT = $this->getNow();
1143 $nowGMT = $this->now;
1145 return $this->asDb($nowGMT);
1149 * Return current date in DB format
1152 public function nowDbDate()
1154 if(!$this->allow_cache) {
1155 $nowGMT = $this->getNow();
1157 $nowGMT = $this->now;
1159 return $this->asDbDate($nowGMT, true);
1163 * Get 'now' DateTime object
1164 * @param bool $userTz return in user timezone?
1165 * @return SugarDateTime
1167 public function getNow($userTz = false)
1169 if(!$this->allow_cache) {
1170 return new SugarDateTime("now", $userTz?$this->_getUserTz():self::$gmtTimezone);
1172 // TODO: should we return clone?
1173 $now = clone $this->now;
1175 return $this->tzUser($now);
1182 * For testability - predictable time value
1183 * @param DateTime $now
1184 * @return TimeDate $this
1186 public function setNow($now)
1193 * Return current datetime in local format
1196 public function now()
1198 return $this->asUser($this->getNow());
1202 * Return current date in User format
1205 public function nowDate()
1207 return $this->asUserDate($this->getNow());
1211 * Get user format's time separator
1214 public function timeSeparator()
1216 if (empty($this->time_separator)) {
1217 $this->time_separator = $this->timeSeparatorFormat($this->get_time_format());
1219 return $this->time_separator;
1223 * Find out format's time separator
1224 * @param string $timeformat Time format
1227 public function timeSeparatorFormat($timeformat)
1229 $date = $this->_convert("00:11:22", self::DB_TIME_FORMAT, null, $timeformat, null);
1230 if (preg_match('/\d+(.+?)11/', $date, $matches)) {
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
1243 * @return array Start & end date in start, startdate, starttime, end, enddate, endtime
1245 public function getDayStartEndGMT($date, User $user = null)
1247 if ($date instanceof DateTime) {
1249 $min->setTimezone($this->_getUserTZ($user));
1251 $max->setTimezone($this->_getUserTZ($user));
1253 $min = new DateTime($date, $this->_getUserTZ($user));
1254 $max = new DateTime($date, $this->_getUserTZ($user));
1256 $min->setTime(0, 0);
1257 $max->setTime(23, 59, 59);
1259 $min->setTimezone(self::$gmtTimezone);
1260 $max->setTimezone(self::$gmtTimezone);
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);
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
1279 public function expandDate($date, $format)
1281 $formats = $this->split_date_time($format);
1282 if(isset($formats[1])) {
1283 return $this->merge_date_time($date, $this->_get_midnight($formats[1]));
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
1296 public function expandTime($date, $format, $tz)
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);
1308 * Get midnight (start of the day) in local time format
1310 * @return Time string
1312 function get_default_midnight()
1314 return $this->_get_midnight($this->get_time_format());
1318 * Get the name of the timezone for the user
1319 * @param User $user User, default - current user
1322 public static function userTimezone(User $user = null)
1324 $user = self::getInstance()->_getUser($user);
1328 $tz = self::getInstance()->_getUserTZ($user);
1330 return $tz->getName();
1336 * Guess the timezone for the current user
1337 * @param int $userOffset Offset from GMT in minutes
1340 public static function guessTimezone($userOffset = 0)
1342 if(!is_numeric($userOffset)) {
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',
1353 $now = new DateTime();
1354 $tzlist = timezone_identifiers_list();
1355 if($userOffset == 0) {
1356 $gmtOffset = date('Z');
1358 if(in_array($nowtz, $tzlist)) {
1359 array_unshift($defaultZones, $nowtz);
1361 $nowtz = timezone_name_from_abbr(date('T'), $gmtOffset, date('I'));
1362 if(in_array($nowtz, $tzlist)) {
1363 array_unshift($defaultZones, $nowtz);
1367 $gmtOffset = $userOffset * 60;
1369 foreach($defaultZones as $zoneName) {
1370 $tz = new DateTimeZone($zoneName);
1371 if($tz->getOffset($now) == $gmtOffset) {
1372 return $tz->getName();
1376 foreach($tzlist as $zoneName) {
1377 $tz = new DateTimeZone($zoneName);
1378 if($tz->getOffset($now) == $gmtOffset) {
1379 return $tz->getName();
1386 * Get the description of the user timezone for specific date
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
1394 public static function userTimezoneSuffix(DateTime $date, User $user = null)
1396 $user = self::getInstance()->_getUser($user);
1400 self::getInstance()->tzUser($date, $user);
1401 return $date->format('T(P)');
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
1410 public static function tzName($name)
1415 if($name instanceof DateTimeZone) {
1418 $tz = timezone_open($name);
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;
1429 return sprintf("%s (GMT%+2d:%02d)%s", str_replace('_',' ', $name), $off/3600, (abs($off)/60)%60, "");//$now->format('I')==1?"(+DST)":"");
1434 * Timezone sorting helper
1441 public static function _sortTz($a, $b)
1443 if($a[0] == $b[0]) {
1444 return strcmp($a[1], $b[1]);
1446 return $a[0]<$b[0]?-1:1;
1451 * Get list of all timezones in the system
1454 public static function getTimezoneList()
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));
1462 uasort($zones, array('TimeDate', '_sortTz'));
1463 foreach($zones as $name => $zonedef) {
1464 $res_zones[$name] = $zonedef[1];
1470 * Print timestamp in RFC2616 format:
1471 * @param int|null $ts Null means current ts
1474 public static function httpTime($ts = null)
1479 return gmdate(self::RFC2616_FORMAT, $ts);
1483 * Create datetime object from calendar array
1484 * @param array $time
1485 * @return SugarDateTime
1487 public function fromTimeArray($time)
1489 if (! isset( $time) || count($time) == 0 )
1491 return $this->nowDb();
1493 elseif ( isset( $time['ts']))
1495 return $this->fromTimestamp($time['ts']);
1497 elseif ( isset( $time['date_str']))
1499 return $this->fromDb($time['date_str']);
1506 $now = $this->getNow(true);
1508 $month = $now->month;
1510 if (isset($time['sec']))
1512 $sec = $time['sec'];
1514 if (isset($time['min']))
1516 $min = $time['min'];
1518 if (isset($time['hour']))
1520 $hour = $time['hour'];
1522 if (isset($time['day']))
1524 $day = $time['day'];
1526 if (isset($time['month']))
1528 $month = $time['month'];
1530 if (isset($time['year']) && $time['year'] >= 1970)
1532 $year = $time['year'];
1534 return $now->setDate($year, $month, $day)->setTime($hour, $min, $sec)->setTimeZone(self::$gmtTimezone);
1540 * Returns the date portion of a datetime string
1542 * @param string $datetime
1545 public function getDatePart($datetime)
1547 list($date, $time) = $this->split_date_time($datetime);
1552 * Returns the time portion of a datetime string
1554 * @param string $datetime
1557 public function getTimePart($datetime)
1559 list($date, $time) = $this->split_date_time($datetime);
1564 * Returns the offset from user's timezone to GMT
1566 * @param DateTime $time When the offset is taken, default is now
1567 * @return int Offset in minutes
1569 public function getUserUTCOffset(User $user = null, DateTime $time = null)
1574 return $this->_getUserTZ($user)->getOffset($time) / 60;
1578 * Create regexp from datetime format
1579 * @param string $format
1580 * @return string Regular expression string
1582 public static function get_regular_expression($format)
1585 $regPositions = array();
1586 $ignoreNextChar = false;
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;
1594 $ignoreNextChar = false;
1595 $newFormat .= $char;
1598 if ($char == "\\") {
1599 $ignoreNextChar = true;
1603 return array('format' => $newFormat, 'positions' => $regPositions);
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"),
1618 * Parse date template
1620 * @param string $template Date expression
1621 * @param bool $daystart Do we want start or end of the day?
1623 * @param bool $adjustForTimezone
1624 * @return SugarDateTime
1626 protected function parseFromTemplate($template, $daystart, User $user = null, $adjustForTimezone = true)
1628 $rawTime = $this->getNow();
1629 $now = $adjustForTimezone?$this->tzUser($rawTime, $user):$rawTime;
1630 if(!empty($template)) {
1631 $now->modify($template);
1634 return $now->get_day_begin();
1636 return $now->get_day_end();
1641 * Get month-long range mdiff months from now
1645 * @param bool $adjustForTimezone
1648 protected function diffMon($mdiff, User $user = null, $adjustForTimezone = true)
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);
1659 * Get year-long range ydiff years from now
1663 * @param bool $adjustForTimezone
1666 protected function diffYear($ydiff, User $user = null, $adjustForTimezone = true)
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);
1677 * Parse date range expression
1678 * Returns beginning and end of the range as a date
1679 * @param string $range
1681 * @param bool $adjustForTimezone Do we need to adjust for timezone?
1682 * @return array of two Date objects, start & end
1684 public function parseDateRange($range, User $user = null, $adjustForTimezone = true)
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)
1693 return $this->diffMon(1, $user, $adjustForTimezone);
1695 return $this->diffMon(-1, $user, $adjustForTimezone);
1697 return $this->diffMon(0, $user, $adjustForTimezone);
1699 return $this->diffYear(-1, $user, $adjustForTimezone);
1701 return $this->diffYear(0, $user, $adjustForTimezone);
1703 return $this->diffYear(1, $user, $adjustForTimezone);
1709 /********************* OLD functions, should not be used publicly anymore ****************/
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
1719 function merge_time_meridiem($date, $format, $mer)
1721 $date = trim($date);
1725 $fakeMerFormat = str_replace(array('a', 'A'), array('@~@', '@~@'), $format);
1726 $noMerFormat = trim(str_replace(array('a', 'A'), array('', ''), $format));
1727 $newDate = $this->swap_formats($date, $noMerFormat, $fakeMerFormat);
1728 return str_replace('@~@', $mer, $newDate);
1732 * @deprecated for public use
1733 * Convert date from one format to another
1735 * @param string $date
1736 * @param string $from
1740 public function swap_formats($date, $from, $to)
1742 return $this->_convert($date, $from, self::$gmtTimezone, $to, self::$gmtTimezone);
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
1756 function handle_offset($date, $format, $to = true, $user = null, $usetimezone = null)
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);
1766 * @deprecated for public use
1767 * Get current GMT datetime in DB format
1770 function get_gmt_db_datetime()
1772 return $this->nowDb();
1776 * @deprecated for public use
1777 * Get current GMT date in DB format
1780 function get_gmt_db_date()
1782 return $this->nowDbDate();
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)
1795 function handleOffsetMax($date)
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);
1802 $min->setTimezone(self::$gmtTimezone);
1803 $max->setTimezone(self::$gmtTimezone);
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);
1810 return $gmtDateTime;
1814 * @deprecated for public use
1815 * this returns the adjustment for a user against the server time
1817 * @return integer number of minutes to adjust a time by to get the appropriate time for the user
1819 public function adjustmentForUserTimeZone()
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;
1830 * @deprecated for public use
1831 * assumes that olddatetime is in Y-m-d H:i:s format
1832 * @param $olddatetime
1835 function convert_to_gmt_datetime($olddatetime)
1837 if (! empty($olddatetime)) {
1838 return date('Y-m-d H:i:s', strtotime($olddatetime) - date('Z'));
1844 * @deprecated for public use
1845 * get user timezone info
1849 public function getUserTimeZone(User $user = null)
1851 $tz = $this->_getUserTZ($user);
1852 return array("gmtOffset" => $tz->getOffset($this->now) / 60);
1856 * @deprecated for public use
1857 * get timezone start & end
1859 * @param string $zone
1862 public function getDSTRange($year, $zone = null)
1865 $tz = timezone_open($zone);
1868 $tz = $this->_getUserTZ();
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);
1878 $transitions = $tz->getTransitions($year_date->ts, $year_end->ts);
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++;
1886 while (isset($transitions[$idx]) && !$transitions[$idx]["isdst"]) $idx++;
1887 if(isset($transitions[$idx])) {
1888 $result["start"] = $this->fromTimestamp($transitions[$idx]["ts"])->asDb();
1891 while (isset($transitions[$idx]) && $transitions[$idx]["isdst"]) $idx++;
1892 if(isset($transitions[$idx])) {
1893 $result["end"] = $this->fromTimestamp($transitions[$idx]["ts"])->asDb();
1898 /****************** GUI stuff that really shouldn't be here, will be moved ************/
1900 * Get Javascript variables setup for user date format validation
1901 * @deprecated moved to SugarView
1902 * @return string JS code
1904 function get_javascript_validation()
1906 return SugarView::getJavascriptValidation();
1911 * This method renders a SELECT HTML form element based on the
1912 * user's time format preferences, with give date's value highlighted.
1914 * If user's prefs have no AM/PM string, returns empty string.
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
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
1926 function AMPMMenu($prefix, $date, $attrs = '')
1928 $tf = $this->get_time_format();
1929 $am = strpbrk($tf, 'aA');
1933 $selected = array("am" => "", "pm" => "", "AM" => "", "PM" => "");
1934 if (preg_match('/([ap]m)/i', $date, $match)) {
1935 $selected[$match[1]] = " selected";
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";
1943 $menu .= "<option value='AM'{$selected["AM"]}>AM";
1944 $menu .= "<option value='PM'{$selected["PM"]}>PM";
1947 return $menu . '</select>';
1951 * Get user format in JS form
1952 * TODO: Remove after full switch to fields
1955 function get_user_date_format()
1957 return str_replace(array('Y', 'm', 'd'), array('yyyy', 'mm', 'dd'), $this->get_date_format());
1961 * Get user time format example
1962 * TODO: Remove after full switch to fields
1966 function get_user_time_format()
1968 global $sugar_config;
1969 $time_pref = $this->get_time_format();
1971 if (! empty($time_pref) && ! empty($sugar_config['time_formats'][$time_pref])) {
1972 return $sugar_config['time_formats'][$time_pref];
1975 return '23:00'; //default