diff --git a/api/src/DateTime.php b/api/src/DateTime.php new file mode 100644 index 0000000000..db9b9b38d2 --- /dev/null +++ b/api/src/DateTime.php @@ -0,0 +1,726 @@ + + * @copyright 2009-16 by RalfBecker@outdoor-training.de + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + * @version $Id$ + */ + +namespace EGroupware\Api; + +// we do not have an own implementation/extensions +use DateTimeZone; + +/** + * EGroupware time and timezone handling class extending PHP's DateTime + * + * Api\DateTime class knows 2 timezones: + * 1. Api\DateTime::$user_timezone timezone of the user, defined in his prefs $GLOBALS['egw_info']['user']['preferences']['common']['tz'] + * 2. Api\DateTime::$server_timezone timezone of the server, read via date_default_timezone_get() + * + * The class extends PHP5.2's DateTime object to: + * - format date and time according to format in user prefs ($type===true: date, $type===false: time, $type==='' date+time) + * - defaulting to a user timezone according to user prefs (not server timezone as DateTime!) + * - deal with integer unix timestamps and DB timestamps in server-time ($type: 'ts'='integer' or 'server' timestamp in servertime) + * + * There are two static methods for simple conversation between server and user time: + * - Api\DateTime::server2user($time,$type=null) + * - Api\DateTime::user2server($time,$type=null) + * (Replacing in 1.6 and previous used adding of tz_offset, which is only correct for current time) + * + * An other static method allows to format any time in several ways: Api\DateTime::to($time,$type) (exceed date($type,$time)). + * + * The constructor of Api\DateTime understand - in addition to DateTime - integer timestamps, array with values for + * keys: ('year', 'month', 'day') or 'full' plus 'hour', 'minute' and optional 'second' or a DateTime object as parameter. + * It defaults to user-time, not server time as DateTime! + * + * The constructor itself throws an Exception in that case (to be precise it does not handle the one thrown by DateTime constructor). + * Static methods server2user, user2server and to return NULL, if given time could not be parsed. + * + * @link http://www.php.net/manual/en/class.datetime.php + * @link http://www.php.net/manual/en/class.datetimezone.php + */ +class DateTime extends \DateTime +{ + /** + * Database timestamp format: Y-m-d H:i:s + */ + const DATABASE = 'Y-m-d H:i:s'; + + /** + * etemplate2 format for ignoring timezones in the browser + */ + const ET2 = 'Y-m-d\TH:i:s\Z'; + /** + * DateTimeZone of server, read from $GLOBALS['egw_info']['server']['server_timezone'], set by self::init() + * + * @var DateTimeZone + */ + static public $server_timezone; + + /** + * DateTimeZone of user, read from user prefs, set by self::init() or self::setUserPrefs() + * + * @var DateTimeZone + */ + static public $user_timezone; + + /** + * Time format from user prefs, set by self::setUserPrefs() + * + * @var string + */ + static public $user_timeformat = 'H:i'; + + /** + * Date format from user prefs, set by self::setUserPrefs() + * + * @var string + */ + static public $user_dateformat = 'Y-m-d'; + + /** + * Constructor + * + * @param int|string|array|DateTime $time ='now' integer timestamp, string with date+time, DateTime object or + * array with values for keys('year','month','day') or 'full' plus 'hour','minute' and optional 'second' + * @param DateTimeZone $tz =null timezone, default user time (PHP DateTime default to server time!) + * @param string &$type=null on return type of $time (optional) + * @throws Exception if $time can NOT be parsed + */ + public function __construct($time='now',DateTimeZone $tz=null,&$type=null) + { + if (is_null($tz)) $tz = self::$user_timezone; // default user timezone + + switch(($type = gettype($time))) + { + case 'NULL': + case 'boolean': // depricated use in calendar for 'now' + $time = 'now'; + $type = 'string'; + // fall through + case 'string': + if (!(is_numeric($time) && ($time > 21000000 || $time < 19000000))) + { + $t_str = $time; + if (is_numeric($time) && strlen($time) == 8) $t_str .= 'T000000'; // 'Ymd' string used in calendar to represent a date + // $time ending in a Z (Zulu or UTC time), is unterstood by DateTime class itself + try { + parent::__construct($t_str,$tz); + break; + } + catch(Exception $e) { + // if string is nummeric, ignore the exception and treat string as timestamp + if (!is_numeric($time)) throw $e; + } + } + $type = 'integer'; + // fall through for timestamps + case 'double': // 64bit integer (timestamps > 2038) are treated on 32bit systems as double + case 'integer': + /* ToDo: Check if PHP5.3 setTimestamp does the same, or always expects UTC timestamp + if (PHP_VERSION >= 5.3) + { + parent::__construct('now',$tz); + $datetime->setTimestamp($time); + } + else*/ + { + parent::__construct(date('Y-m-d H:i:s',$time),$tz); + } + break; + + case 'array': + parent::__construct('now',$tz); + if (isset($time['Y'])) // array format used in eTemplate + { + $time = array( + 'year' => $time['Y'], + 'month' => $time['m'], + 'day' => $time['d'], + 'hour' => $time['H'], + 'minute' => $time['i'], + 'second' => $time['s'], + ); + } + if (!empty($time['full']) && empty($time['year'])) + { + $time['year'] = (int)substr($time['full'],0,4); + $time['month'] = (int)substr($time['full'],4,2); + $time['day'] = (int)substr($time['full'],6,2); + } + if (isset($time['year'])) $this->setDate((int)$time['year'],(int)$time['month'],isset($time['day']) ? (int)$time['day'] : (int)$time['mday']); + $this->setTime((int)$time['hour'],(int)$time['minute'],(int)$time['second']); + break; + + case 'object': + if ($time instanceof \DateTime) + { + parent::__construct($time->format('Y-m-d H:i:s'),$time->getTimezone()); + $this->setTimezone($tz); + break; + } + // fall through + default: + throw new egw_exception_assertion_failed("Not implemented for type ($type)$time!"); + } + } + + /** + * Like DateTime::add, but additional allow to use a string run through DateInterval::createFromDateString + * + * @param DateInterval|string $interval eg. '1 day', '-2 weeks' + */ + public function add($interval) + { + if (is_string($interval)) $interval = DateInterval::createFromDateString($interval); + + parent::add($interval); + } + + /** + * Set date to beginning of the week taking into account calendar weekdaystarts preference + */ + public function setWeekstart() + { + $wday = (int) $this->format('w'); // 0=sun, ..., 6=sat + switch($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts']) + { + case 'Sunday': + $wstart = -$wday; + break; + case 'Saturday': + $wstart = -(6-$wday); + break; + case 'Moday': + default: + $wstart = -($wday ? $wday-1 : 6); + break; + } + if ($wstart) $this->add($wstart.'days'); + } + + /** + * return SQL implementing filtering by date + * + * @param string $name + * @param int &$start + * @param int &$end + * @param string $column name of timestamp column to use in returned sql + * @param array $filters $name => list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) pairs with offsets + * @return string + */ + public static function sql_filter($name, &$start, &$end, $column, array $filters=array()) + { + if ($name == 'custom' && $start) + { + $start = new DateTime($start); + $start->setTime(0, 0, 0); + + if ($end) + { + $end = new DateTime($end); + $end->setTime(0, 0, 0); + $end->add('+1day'); + } + else + { + $end = new DateTime($start); + $end->add('+1week'); + } + } + else + { + if (!isset($filters[$name])) + { + return '1=1'; + } + $start = new DateTime('now'); + $start->setTime(0, 0, 0); + $end = new DateTime('now'); + $end->setTime(0, 0, 0); + + $year = (int) $start->format('Y'); + $month = (int) $start->format('m'); + + list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) = $filters[$name]; + + // Handle quarters + if(stripos($name, 'quarter') !== false) + { + $start->setDate($year, ((int)floor(($smonth+$month) / 3.1)) * 3 + 1, 1); + $end->setDate($year, ((int)floor(($emonth+$month) / 3.1)+1) * 3 + 1, 1); + } + elseif ($syear || $eyear) + { + $start->setDate($year+$syear, 1, 1); + $end->setDate($year+$eyear, 1, 1); + } + elseif ($smonth || $emonth) + { + $start->setDate($year, $month+$smonth, 1); + $end->setDate($year, $month+$emonth, 1); + } + elseif ($sday || $eday) + { + if ($sday) $start->add($sday.'days'); + if ($eday) $end->add($eday.'days'); + } + elseif ($sweek || $eweek) + { + $start->setWeekstart(); + if ($sweek) $start->add($sweek.'weeks'); + $end->setWeekstart(); + if ($eweek) $end->add($eweek.'weeks'); + } + } + // convert start + end from user to servertime for the filter + $sql = '('.DateTime::user2server($start, 'ts').' <= '.$column.' AND '.$column.' < '.DateTime::user2server($end, 'ts').')'; + //error_log(__METHOD__."('$name', ...) syear=$syear, smonth=$smonth, sday=$sday, sweek=$sweek, eyear=$eyear, emonth=$emonth, eday=$eday, eweek=$eweek --> start=".$start->format().', end='.$end->format().", sql='$sql'"); + + // returned timestamps: $end is an inclusive date, eg. for today it's equal to start! + $start = $start->format('ts'); + $end->add('-1day'); + $end = $end->format('ts'); + + return $sql; + } + + /** + * Set user timezone, according to user prefs: converts current time to user time + * + * Does nothing if self::$user_timezone is current timezone! + */ + public function setUser() + { + $this->setTimezone(self::$user_timezone); + } + + /** + * Set server timezone: converts current time to server time + * + * Does nothing if self::$server_timezone is current timezone! + */ + public function setServer() + { + $this->setTimezone(self::$server_timezone); + } + + /** + * Format DateTime object as a specific type or string + * + * @param string $type ='' 'integer'|'ts'=timestamp, 'server'=timestamp in servertime, 'string'='Y-m-d H:i:s', 'object'=DateTime, + * 'array'=array with values for keys ('year','month','day','hour','minute','second','full','raw') or string with format + * true = date only, false = time only as in user prefs, '' = date+time as in user prefs + * @return int|string|array|datetime see $type + */ + public function format($type='') + { + switch((string)$type) + { + case '': // empty string: date and time as in user prefs + //case '': // boolean false: time as in user prefs + case '1': // boolean true: date as in user prefs + if (is_bool($type)) + { + $type = $type ? self::$user_dateformat : self::$user_timeformat; + } + else + { + $type = self::$user_dateformat.', '.self::$user_timeformat; + } + break; + + case 'string': + $type = self::DATABASE; + break; + + case 'server': // timestamp in servertime + $this->setServer(); + // fall through + case 'integer': + case 'ts': + // ToDo: Check if PHP5.3 getTimestamp does the same, or always returns UTC timestamp + return mktime(parent::format('H'),parent::format('i'),parent::format('s'),parent::format('m'),parent::format('d'),parent::format('Y')); + + case 'object': + case 'datetime': + case 'egw_time': + case 'DateTime': + return clone($this); + + case 'array': + $arr = array( + 'year' => (int)parent::format('Y'), + 'month' => (int)parent::format('m'), + 'day' => (int)parent::format('d'), + 'hour' => (int)parent::format('H'), + 'minute' => (int)parent::format('i'), + 'second' => (int)parent::format('s'), + 'full' => parent::format('Ymd'), + ); + $arr['raw'] = mktime($arr['hour'],$arr['minute'],$arr['second'],$arr['month'],$arr['day'],$arr['year']); + return $arr; + + case 'date_array': // array with short keys used by date: Y, m, d, H, i, s (used in eTemplate) + return array( + 'Y' => (int)parent::format('Y'), + 'm' => (int)parent::format('m'), + 'd' => (int)parent::format('d'), + 'H' => (int)parent::format('H'), + 'i' => (int)parent::format('i'), + 's' => (int)parent::format('s'), + ); + } + // default $type contains string with format + return parent::format($type); + } + + /** + * Cast object to string + * + * @return string eg. "Wednesday, 2009-11-11 11:11:11 (Europe/Berlin)" + */ + public function __toString() + { + return $this->format('l, '.self::DATABASE).' ('.$this->getTimezone()->getName().')'; + } + + /** + * Convert a server time into a user time + * + * @param int|string|array|DateTime $time + * @param string $type =null type or return-value, default (null) same as $time + * @return int|string|array|datetime null if time could not be parsed + */ + public static function server2user($time,$type=null) + { + $typeof='DateTime'; + if (!($time instanceof DateTime)) + { + try + { + $time = new DateTime($time, self::$server_timezone, $typeof); + } + catch(Exception $e) + { + unset($e); + return null; // time could not be parsed + } + } + $time->setUser(); + + if (is_null($type)) $type = $typeof; + + //echo "

".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."

\n"; + return $time->format($type); + } + + /** + * Convert a user time into a server time + * + * @param int|string|array|datetime $time + * @param string $type =null type or return-value, default (null) same as $time + * @return int|string|array|datetime null if time could not be parsed + */ + public static function user2server($time,$type=null) + { + $typeof='DateTime'; + if (!($time instanceof DateTime)) + { + try + { + $time = new DateTime($time,self::$user_timezone,$typeof); + } + catch(Exception $e) + { + unset($e); + return null; // time could not be parsed + } + } + $time->setServer(); + + if (is_null($type)) $type = $typeof; + + //echo "

".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."

\n"; + return $time->format($type); + } + + /** + * Convert time to a specific format or string, static version of DateTime::format() + * + * @param int|string|array|DateTime $time ='now' see constructor + * @param string $type ='' 'integer'|'ts'=timestamp, 'server'=timestamp in servertime, 'string'='Y-m-d H:i:s', 'object'=DateTime, + * 'array'=array with values for keys ('year','month','day','hour','minute','second','full','raw') or string with format + * true = date only, false = time only as in user prefs, '' = date+time as in user prefs + * @return int|string|array|datetime see $type, null if time could not be parsed + */ + public static function to($time='now',$type='') + { + if (!($time instanceof DateTime)) + { + try + { + $time = new DateTime($time); + } + catch(Exception $e) + { + unset($e); + return null; // time could not be parsed + } + } + return $time->format($type); + } + + /** + * Setter for user timezone, should be called after reading user preferences + * + * @param string $tz timezone, eg. 'Europe/Berlin' or 'UTC' + * @param string $dateformat ='' eg. 'Y-m-d' or 'd.m.Y' + * @param string|int $timeformat ='' integer 12, 24, or format string eg. 'H:i' + * @throws egw_exception_wrong_userinput if invalid $tz parameter + * @return DateTimeZone + */ + public static function setUserPrefs($tz,$dateformat='',$timeformat='') + { + //echo "

".__METHOD__."('$tz','$dateformat','$timeformat') ".function_backtrace()."

\n"; + if (!empty($dateformat)) self::$user_dateformat = $dateformat; + + switch($timeformat) + { + case '': + break; + case '24': + self::$user_timeformat = 'H:i'; + break; + case '12': + self::$user_timeformat = 'h:i a'; + break; + default: + self::$user_timeformat = $timeformat; + break; + } + try { + self::$user_timezone = new DateTimeZone($tz); + } + catch(Exception $e) + { + unset($e); + // silently use server timezone, as we have no means to report the wrong timezone to the user from this class + self::$user_timezone = clone(self::$server_timezone); + } + return self::$user_timezone; + } + + /** + * Get offset in seconds between user and server time at given time $time + * + * Compatibility method for old code. It is only valid for the given time, because of possible daylight saving changes! + * + * @param int|string|DateTime $time ='now' + * @return int difference in seconds between user and server time (for the given time!) + */ + public static function tz_offset_s($time='now') + { + if (!($time instanceof DateTime)) $time = new DateTime($time); + + return self::$user_timezone->getOffset($time) - self::$server_timezone->getOffset($time); + } + + /** + * Init static variables, reading user prefs + */ + public static function init() + { + // if no server timezone set, use date_default_timezone_get() to determine it + if (empty($GLOBALS['egw_info']['server']['server_timezone'])) + { + $GLOBALS['egw_info']['server']['server_timezone'] = date_default_timezone_get(); + } + // make sure we have a valid server timezone set + try { + self::$server_timezone = new DateTimeZone($GLOBALS['egw_info']['server']['server_timezone']); + } + catch(Exception $e) + { + try { + self::$server_timezone = new DateTimeZone(date_default_timezone_get()); + } + catch(Exception $e) + { + self::$server_timezone = new DateTimeZone('Europe/Berlin'); + } + error_log(__METHOD__."() invalid server_timezone='{$GLOBALS['egw_info']['server']['server_timezone']}' setting now '".self::$server_timezone->getName()."'!"); + config::save_value('server_timezone',$GLOBALS['egw_info']['server']['server_timezone'] = self::$server_timezone->getName(),'phpgwapi'); + } + if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['tz'])) + { + $GLOBALS['egw_info']['user']['preferences']['common']['tz'] = $GLOBALS['egw_info']['server']['server_timezone']; + } + self::setUserPrefs($GLOBALS['egw_info']['user']['preferences']['common']['tz'], + $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'], + $GLOBALS['egw_info']['user']['preferences']['common']['timeformat']); + } + + /** + * Return "beautified" timezone list: + * - no depricated timezones + * - return UTC and oceans at the end + * - if (user lang is a european language), move Europe to top + * + * @return array continent|ocean => array(tz-name => tz-label incl. current time) + */ + public static function getTimezones() + { + // prepare list of timezones from php, ignoring depricated ones and sort as follows + $tzs = array( + 'Africa' => array(), // Contients + 'America' => array(), + 'Asia' => array(), + 'Australia' => array(), + 'Europe' => array(), + 'Atlantic' => array(), // Oceans + 'Pacific' => array(), + 'Indian' => array(), + 'Antarctica' => array(), // Poles + 'Arctic' => array(), + 'UTC' => array('UTC' => 'UTC'), + ); + // no VTIMEZONE available in calendar_timezones --> do NOT return them + static $no_vtimezone = array( + 'Europe/Tiraspol', + 'America/Atka', + 'America/Buenos_Aires', + 'America/Catamarca', + 'America/Coral_Harbour', + 'America/Cordoba', + 'America/Ensenada', + 'America/Fort_Wayne', + 'America/Indianapolis', + 'America/Jujuy', + 'America/Knox_IN', + 'America/Mendoza', + 'America/Porto_Acre', + 'America/Rosario', + 'America/Virgin', + 'Asia/Ashkhabad', + 'Asia/Beijing', + 'Asia/Chungking', + 'Asia/Dacca', + 'Asia/Macao', + 'Asia/Riyadh87', + 'Asia/Riyadh88', + 'Asia/Riyadh89', + 'Asia/Tel_Aviv', + 'Asia/Thimbu', + 'Asia/Ujung_Pandang', + 'Asia/Ulan_Bator', + 'Australia/ACT', + 'Australia/Canberra', + 'Australia/LHI', + 'Australia/North', + 'Australia/NSW', + 'Australia/Queensland', + 'Australia/South', + 'Australia/Tasmania', + 'Australia/Victoria', + 'Australia/West', + 'Australia/Yancowinna', + 'Pacific/Samoa', + ); + foreach(DateTimeZone::listIdentifiers() as $name) + { + if (in_array($name,$no_vtimezone)) continue; // do NOT allow to set in EGroupware, as we have not VTIMEZONE component for it + list($continent) = explode('/',$name,2); + if (!isset($tzs[$continent])) continue; // old depricated timezones + $datetime = new DateTime('now',new DateTimeZone($name)); + $tzs[$continent][$name] = str_replace(array('_','/'),array(' ',' / '),$name)." ".$datetime->format(); + unset($datetime); + } + foreach($tzs as $continent => &$data) + { + natcasesort($data); // sort cities + } + unset($data); + + // if user lang or installed langs contain a european language --> move Europe to top of tz list + $langs = class_exists('translation') ? translation::get_installed_langs() : array(); + if (array_intersect(array($GLOBALS['egw_info']['user']['preferences']['common']['lang'])+array_keys($langs), + array('de','fr','it','nl','bg','ca','cs','da','el','es-es','et','eu','fi','hr','hu','lt','no','pl','pt','sk','sl','sv','tr','uk'))) + { + $tzs = array_merge(array('Europe' => $tzs['Europe']),$tzs); + } + return $tzs; + } + + /** + * Get user timezones (the ones user selected in his prefs), plus evtl. an extra one + * + * @param string $extra extra timezone to add, if not already included in user timezones + * @return array tzid => label + */ + public static function getUserTimezones($extra=null) + { + $tz = $GLOBALS['egw_info']['user']['preferences']['common']['tz']; + $user_tzs = explode(',',$GLOBALS['egw_info']['user']['preferences']['common']['tz_selection']); + if (count($user_tzs) <= 1) + { + $user_tzs = $tz ? array($tz) : array(); + } + if ($tz && !in_array($tz,$user_tzs)) + { + $user_tzs = array_merge(array($tz),$user_tzs); + } + if (!$user_tzs) // if we have no user timezones, eg. user set no pref --> use server default + { + $user_tzs = array($GLOBALS['egw_info']['server']['server_timezone']); + } + if ($extra && !in_array($extra,$user_tzs)) + { + $user_tzs = array_merge(array($extra),$user_tzs); + } + $ret_user_tzs = array_combine($user_tzs,$user_tzs); + foreach($ret_user_tzs as &$label) + { + $label = str_replace(array('_','/'),array(' ',' / '),$label); + } + return $ret_user_tzs; + } +} +DateTime::init(); + +/* +if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) // some tests +{ + // test timestamps/dates before 1970 + foreach(array('19690811',-3600,'-119322000') as $ts) + { + try { + echo "

DateTime::to($ts,'Y-m-d H:i:s')=".DateTime::to($ts,'Y-m-d H:i:s')."

\n"; + $et = new DateTime($ts); + echo "

DateTime($ts)->format('Y-m-d H:i:s')=".$et->format('Y-m-d H:i:s')."

\n"; + $dt = new DateTime($ts); + echo "

DateTime($ts)->format('Y-m-d H:i:s')=".$dt->format('Y-m-d H:i:s')."

\n"; + } catch(Exception $e) { + echo "

Exception: ".$e->getMessage()."

\n"; + } + } + // user time is UTC + echo "

user timezone = ".($GLOBALS['egw_info']['user']['preferences']['common']['tz'] = 'UTC').", server timezone = ".date_default_timezone_get()."

\n"; + + $time = time(); + echo "

time=$time=".date('Y-m-d H:i:s',$time)."(server) =".DateTime::server2user($time,'Y-m-d H:i:s')."(user) =".DateTime::server2user($time,'ts')."(user)=".date('Y-m-d H:i:s',DateTime::server2user($time,'ts'))."

\n"; + + echo "DateTime::to(array('full' => '20091020', 'hour' => 12, 'minute' => 0))='".DateTime::to(array('full' => '20091020', 'hour' => 12, 'minute' => 0))."'

\n"; + + $ts = DateTime::to(array('full' => '20091027', 'hour' => 10, 'minute' => 0),'ts'); + echo "

2009-10-27 10h UTC timestamp=$ts --> server time = ".DateTime::user2server($ts,'')." --> user time = ".DateTime::server2user(DateTime::user2server($ts),'')."

\n"; + + $ts = DateTime::to(array('full' => '20090627', 'hour' => 10, 'minute' => 0),'ts'); + echo "

2009-06-27 10h UTC timestamp=$ts --> server time = ".DateTime::user2server($ts,'')." --> user time = ".DateTime::server2user(DateTime::user2server($ts),'')."

\n"; +} +*/ diff --git a/api/src/Db.php b/api/src/Db.php index 5029c3be78..05ebfac77a 100644 --- a/api/src/Db.php +++ b/api/src/Db.php @@ -1350,9 +1350,9 @@ class Db { case 'int': // if DateTime object given, convert it to a unix timestamp (NOT converting the timezone!) - if (is_object($value) && ($value instanceof DateTime)) + if (is_object($value) && ($value instanceof \DateTime)) { - return ($value instanceof egw_time) ? $value->format('ts') : egw_time::to($value,'ts'); + return ($value instanceof DateTime) ? $value->format('ts') : DateTime::to($value,'ts'); } case 'auto': // atm. (php5.2) php has only 32bit integers, it converts everything else to float. @@ -1386,14 +1386,14 @@ class Db break; // handled like strings case 'date': // if DateTime object given, convert it (NOT converting the timezone!) - if (is_object($value) && ($value instanceof DateTime)) + if (is_object($value) && ($value instanceof \DateTime)) { return $this->Link_ID->qstr($value->format('Y-m-d')); } return $this->Link_ID->DBDate($value); case 'timestamp': // if DateTime object given, convert it (NOT converting the timezone!) - if (is_object($value) && ($value instanceof DateTime)) + if (is_object($value) && ($value instanceof \DateTime)) { return $this->Link_ID->qstr($value->format('Y-m-d H:i:s')); } diff --git a/api/src/Vfs.php b/api/src/Vfs.php index 3edf5ffa48..cd44a6c3d6 100644 --- a/api/src/Vfs.php +++ b/api/src/Vfs.php @@ -18,7 +18,6 @@ use mime_magic; use common; use html; use HTTP_WebDAV_Server; -use egw_time; /** * Class containing static methods to use the new eGW virtual file system @@ -1561,7 +1560,7 @@ class Vfs extends Vfs\StreamWrapper } // Set a comment to help tell them apart - $zip->setArchiveComment(lang('Created by %1', $GLOBALS['egw_info']['user']['account_lid']) . ' ' .egw_time::to()); + $zip->setArchiveComment(lang('Created by %1', $GLOBALS['egw_info']['user']['account_lid']) . ' ' .DateTime::to()); // Record total for debug, not available after close() $total_files = $zip->numFiles; diff --git a/phpgwapi/inc/class.egw_time.inc.php b/phpgwapi/inc/class.egw_time.inc.php index 2b6e059a4f..c8366705e6 100644 --- a/phpgwapi/inc/class.egw_time.inc.php +++ b/phpgwapi/inc/class.egw_time.inc.php @@ -5,714 +5,16 @@ * @package api * @link http://www.egroupware.org * @author Ralf Becker - * @copyright 2009 by RalfBecker@outdoor-training.de + * @copyright 2009-16 by RalfBecker@outdoor-training.de * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @version $Id$ */ +use EGroupware\Api; + /** * EGroupware time and timezone handling class extending PHP's DateTime * - * egw_time class knows 2 timezones: - * 1. egw_time::$user_timezone timezone of the user, defined in his prefs $GLOBALS['egw_info']['user']['preferences']['common']['tz'] - * 2. egw_time::$server_timezone timezone of the server, read via date_default_timezone_get() - * - * The class extends PHP5.2's DateTime object to: - * - format date and time according to format in user prefs ($type===true: date, $type===false: time, $type==='' date+time) - * - defaulting to a user timezone according to user prefs (not server timezone as DateTime!) - * - deal with integer unix timestamps and DB timestamps in server-time ($type: 'ts'='integer' or 'server' timestamp in servertime) - * - * There are two static methods for simple conversation between server and user time: - * - egw_time::server2user($time,$type=null) - * - egw_time::user2server($time,$type=null) - * (Replacing in 1.6 and previous used adding of tz_offset, which is only correct for current time) - * - * An other static method allows to format any time in several ways: egw_time::to($time,$type) (exceed date($type,$time)). - * - * The constructor of egw_time understand - in addition to DateTime - integer timestamps, array with values for - * keys: ('year', 'month', 'day') or 'full' plus 'hour', 'minute' and optional 'second' or a DateTime object as parameter. - * It defaults to user-time, not server time as DateTime! - * - * The constructor itself throws an Exception in that case (to be precise it does not handle the one thrown by DateTime constructor). - * Static methods server2user, user2server and to return NULL, if given time could not be parsed. - * - * @link http://www.php.net/manual/en/class.datetime.php - * @link http://www.php.net/manual/en/class.datetimezone.php + * @deprecated use Api\DateTime */ -class egw_time extends DateTime -{ - /** - * Database timestamp format: Y-m-d H:i:s - */ - const DATABASE = 'Y-m-d H:i:s'; - - /** - * etemplate2 format for ignoring timezones in the browser - */ - const ET2 = 'Y-m-d\TH:i:s\Z'; - /** - * DateTimeZone of server, read from $GLOBALS['egw_info']['server']['server_timezone'], set by self::init() - * - * @var DateTimeZone - */ - static public $server_timezone; - - /** - * DateTimeZone of user, read from user prefs, set by self::init() or self::setUserPrefs() - * - * @var DateTimeZone - */ - static public $user_timezone; - - /** - * Time format from user prefs, set by self::setUserPrefs() - * - * @var string - */ - static public $user_timeformat = 'H:i'; - - /** - * Date format from user prefs, set by self::setUserPrefs() - * - * @var string - */ - static public $user_dateformat = 'Y-m-d'; - - /** - * Constructor - * - * @param int|string|array|DateTime $time='now' integer timestamp, string with date+time, DateTime object or - * array with values for keys('year','month','day') or 'full' plus 'hour','minute' and optional 'second' - * @param DateTimeZone $tz=null timezone, default user time (PHP DateTime default to server time!) - * @param string &$type=null on return type of $time (optional) - * @throws Exception if $time can NOT be parsed - * @return egw_time - */ - public function __construct($time='now',DateTimeZone $tz=null,&$type=null) - { - if (is_null($tz)) $tz = self::$user_timezone; // default user timezone - - switch(($type = gettype($time))) - { - case 'NULL': - case 'boolean': // depricated use in calendar for 'now' - $time = 'now'; - $type = 'string'; - // fall through - case 'string': - if (!(is_numeric($time) && ($time > 21000000 || $time < 19000000))) - { - $t_str = $time; - if (is_numeric($time) && strlen($time) == 8) $t_str .= 'T000000'; // 'Ymd' string used in calendar to represent a date - // $time ending in a Z (Zulu or UTC time), is unterstood by DateTime class itself - try { - parent::__construct($t_str,$tz); - break; - } - catch(Exception $e) { - // if string is nummeric, ignore the exception and treat string as timestamp - if (!is_numeric($time)) throw $e; - } - } - $type = 'integer'; - // fall through for timestamps - case 'double': // 64bit integer (timestamps > 2038) are treated on 32bit systems as double - case 'integer': - /* ToDo: Check if PHP5.3 setTimestamp does the same, or always expects UTC timestamp - if (PHP_VERSION >= 5.3) - { - parent::__construct('now',$tz); - $datetime->setTimestamp($time); - } - else*/ - { - parent::__construct(date('Y-m-d H:i:s',$time),$tz); - } - break; - - case 'array': - parent::__construct('now',$tz); - if (isset($time['Y'])) // array format used in eTemplate - { - $time = array( - 'year' => $time['Y'], - 'month' => $time['m'], - 'day' => $time['d'], - 'hour' => $time['H'], - 'minute' => $time['i'], - 'second' => $time['s'], - ); - } - if (!empty($time['full']) && empty($time['year'])) - { - $time['year'] = (int)substr($time['full'],0,4); - $time['month'] = (int)substr($time['full'],4,2); - $time['day'] = (int)substr($time['full'],6,2); - } - if (isset($time['year'])) $this->setDate((int)$time['year'],(int)$time['month'],isset($time['day']) ? (int)$time['day'] : (int)$time['mday']); - $this->setTime((int)$time['hour'],(int)$time['minute'],(int)$time['second']); - break; - - case 'object': - if ($time instanceof DateTime) - { - parent::__construct($time->format('Y-m-d H:i:s'),$time->getTimezone()); - $this->setTimezone($tz); - break; - } - // fall through - default: - throw new egw_exception_assertion_failed("Not implemented for type ($type)$time!"); - } - } - - /** - * Like DateTime::add, but additional allow to use a string run through DateInterval::createFromDateString - * - * @param DateInterval|string $interval eg. '1 day', '-2 weeks' - */ - public function add($interval) - { - if (is_string($interval)) $interval = DateInterval::createFromDateString($interval); - - parent::add($interval); - } - - /** - * Set date to beginning of the week taking into account calendar weekdaystarts preference - */ - public function setWeekstart() - { - $wday = (int) $this->format('w'); // 0=sun, ..., 6=sat - switch($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts']) - { - case 'Sunday': - $wstart = -$wday; - break; - case 'Saturday': - $wstart = -(6-$wday); - break; - case 'Moday': - default: - $wstart = -($wday ? $wday-1 : 6); - break; - } - if ($wstart) $this->add($wstart.'days'); - } - - /** - * return SQL implementing filtering by date - * - * @param string $name - * @param int &$start - * @param int &$end - * @param string $column name of timestamp column to use in returned sql - * @param array $filters $name => list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) pairs with offsets - * @return string - */ - public static function sql_filter($name, &$start, &$end, $column, array $filters=array()) - { - if ($name == 'custom' && $start) - { - $start = new egw_time($start); - $start->setTime(0, 0, 0); - - if ($end) - { - $end = new egw_time($end); - $end->setTime(0, 0, 0); - $end->add('+1day'); - } - else - { - $end = new egw_time($start); - $end->add('+1week'); - } - } - else - { - if (!isset($filters[$name])) - { - return '1=1'; - } - $start = new egw_time('now'); - $start->setTime(0, 0, 0); - $end = new egw_time('now'); - $end->setTime(0, 0, 0); - - $year = (int) $start->format('Y'); - $month = (int) $start->format('m'); - - list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) = $filters[$name]; - - // Handle quarters - if(stripos($name, 'quarter') !== false) - { - $start->setDate($year, ((int)floor(($smonth+$month) / 3.1)) * 3 + 1, 1); - $end->setDate($year, ((int)floor(($emonth+$month) / 3.1)+1) * 3 + 1, 1); - } - elseif ($syear || $eyear) - { - $start->setDate($year+$syear, 1, 1); - $end->setDate($year+$eyear, 1, 1); - } - elseif ($smonth || $emonth) - { - $start->setDate($year, $month+$smonth, 1); - $end->setDate($year, $month+$emonth, 1); - } - elseif ($sday || $eday) - { - if ($sday) $start->add($sday.'days'); - if ($eday) $end->add($eday.'days'); - } - elseif ($sweek || $eweek) - { - $start->setWeekstart(); - if ($sweek) $start->add($sweek.'weeks'); - $end->setWeekstart(); - if ($eweek) $end->add($eweek.'weeks'); - } - } - // convert start + end from user to servertime for the filter - $sql = '('.egw_time::user2server($start, 'ts').' <= '.$column.' AND '.$column.' < '.egw_time::user2server($end, 'ts').')'; - //error_log(__METHOD__."('$name', ...) syear=$syear, smonth=$smonth, sday=$sday, sweek=$sweek, eyear=$eyear, emonth=$emonth, eday=$eday, eweek=$eweek --> start=".$start->format().', end='.$end->format().", sql='$sql'"); - - // returned timestamps: $end is an inclusive date, eg. for today it's equal to start! - $start = $start->format('ts'); - $end->add('-1day'); - $end = $end->format('ts'); - - return $sql; - } - - /** - * Set user timezone, according to user prefs: converts current time to user time - * - * Does nothing if self::$user_timezone is current timezone! - */ - public function setUser() - { - $this->setTimezone(self::$user_timezone); - } - - /** - * Set server timezone: converts current time to server time - * - * Does nothing if self::$server_timezone is current timezone! - */ - public function setServer() - { - $this->setTimezone(self::$server_timezone); - } - - /** - * Format DateTime object as a specific type or string - * - * @param string $type='' 'integer'|'ts'=timestamp, 'server'=timestamp in servertime, 'string'='Y-m-d H:i:s', 'object'=DateTime, - * 'array'=array with values for keys ('year','month','day','hour','minute','second','full','raw') or string with format - * true = date only, false = time only as in user prefs, '' = date+time as in user prefs - * @return int|string|array|datetime see $type - */ - public function format($type='') - { - switch((string)$type) - { - case '': // empty string: date and time as in user prefs - //case '': // boolean false: time as in user prefs - case '1': // boolean true: date as in user prefs - if (is_bool($type)) - { - $type = $type ? self::$user_dateformat : self::$user_timeformat; - } - else - { - $type = self::$user_dateformat.', '.self::$user_timeformat; - } - break; - - case 'string': - $type = self::DATABASE; - break; - - case 'server': // timestamp in servertime - $this->setServer(); - // fall through - case 'integer': - case 'ts': - // ToDo: Check if PHP5.3 getTimestamp does the same, or always returns UTC timestamp - return mktime(parent::format('H'),parent::format('i'),parent::format('s'),parent::format('m'),parent::format('d'),parent::format('Y')); - - case 'object': - case 'datetime': - case 'egw_time': - return clone($this); - - case 'array': - $arr = array( - 'year' => (int)parent::format('Y'), - 'month' => (int)parent::format('m'), - 'day' => (int)parent::format('d'), - 'hour' => (int)parent::format('H'), - 'minute' => (int)parent::format('i'), - 'second' => (int)parent::format('s'), - 'full' => parent::format('Ymd'), - ); - $arr['raw'] = mktime($arr['hour'],$arr['minute'],$arr['second'],$arr['month'],$arr['day'],$arr['year']); - return $arr; - - case 'date_array': // array with short keys used by date: Y, m, d, H, i, s (used in eTemplate) - return array( - 'Y' => (int)parent::format('Y'), - 'm' => (int)parent::format('m'), - 'd' => (int)parent::format('d'), - 'H' => (int)parent::format('H'), - 'i' => (int)parent::format('i'), - 's' => (int)parent::format('s'), - ); - } - // default $type contains string with format - return parent::format($type); - } - - /** - * Cast object to string - * - * @return string eg. "Wednesday, 2009-11-11 11:11:11 (Europe/Berlin)" - */ - public function __toString() - { - return $this->format('l, '.self::DATABASE).' ('.$this->getTimezone()->getName().')'; - } - - /** - * Convert a server time into a user time - * - * @param int|string|array|DateTime $time - * @param string $type=null type or return-value, default (null) same as $time - * @return int|string|array|datetime null if time could not be parsed - */ - public static function server2user($time,$type=null) - { - $typeof='egw_time'; - if (!($time instanceof egw_time)) - { - try - { - $time = new egw_time($time,self::$server_timezone,$typeof); - } - catch(Exception $e) - { - return null; // time could not be parsed - } - } - $time->setUser(); - - if (is_null($type)) $type = $typeof; - - //echo "

".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."

\n"; - return $time->format($type); - } - - /** - * Convert a user time into a server time - * - * @param int|string|array|datetime $time - * @param string $type=null type or return-value, default (null) same as $time - * @return int|string|array|datetime null if time could not be parsed - */ - public static function user2server($time,$type=null) - { - $typeof='egw_time'; - if (!($time instanceof egw_time)) - { - try - { - $time = new egw_time($time,self::$user_timezone,$typeof); - } - catch(Exception $e) - { - return null; // time could not be parsed - } - } - $time->setServer(); - - if (is_null($type)) $type = $typeof; - - //echo "

".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."

\n"; - return $time->format($type); - } - - /** - * Convert time to a specific format or string, static version of egw_time::format() - * - * @param int|string|array|DateTime $time='now' see constructor - * @param string $type='' 'integer'|'ts'=timestamp, 'server'=timestamp in servertime, 'string'='Y-m-d H:i:s', 'object'=DateTime, - * 'array'=array with values for keys ('year','month','day','hour','minute','second','full','raw') or string with format - * true = date only, false = time only as in user prefs, '' = date+time as in user prefs - * @return int|string|array|datetime see $type, null if time could not be parsed - */ - public static function to($time='now',$type='') - { - if (!($time instanceof egw_time)) - { - try - { - $time = new egw_time($time); - } - catch(Exception $e) - { - return null; // time could not be parsed - } - } - return $time->format($type); - } - - /** - * Setter for user timezone, should be called after reading user preferences - * - * @param string $tz timezone, eg. 'Europe/Berlin' or 'UTC' - * @param string $dateformat='' eg. 'Y-m-d' or 'd.m.Y' - * @param string|int $timeformat='' integer 12, 24, or format string eg. 'H:i' - * @throws egw_exception_wrong_userinput if invalid $tz parameter - * @return DateTimeZone - */ - public static function setUserPrefs($tz,$dateformat='',$timeformat='') - { - //echo "

".__METHOD__."('$tz','$dateformat','$timeformat') ".function_backtrace()."

\n"; - if (!empty($dateformat)) self::$user_dateformat = $dateformat; - - switch($timeformat) - { - case '': - break; - case '24': - self::$user_timeformat = 'H:i'; - break; - case '12': - self::$user_timeformat = 'h:i a'; - break; - default: - self::$user_timeformat = $timeformat; - break; - } - try { - self::$user_timezone = new DateTimeZone($tz); - } - catch(Exception $e) - { - // silently use server timezone, as we have no means to report the wrong timezone to the user from this class - self::$user_timezone = clone(self::$server_timezone); - } - return self::$user_timezone; - } - - /** - * Get offset in seconds between user and server time at given time $time - * - * Compatibility method for old code. It is only valid for the given time, because of possible daylight saving changes! - * - * @param int|string|DateTime $time='now' - * @return int difference in seconds between user and server time (for the given time!) - */ - public static function tz_offset_s($time='now') - { - if (!($time instanceof DateTime)) $time = new egw_time($time); - - return egw_time::$user_timezone->getOffset($time) - egw_time::$server_timezone->getOffset($time); - } - - /** - * Init static variables, reading user prefs - */ - public static function init() - { - // if no server timezone set, use date_default_timezone_get() to determine it - if (empty($GLOBALS['egw_info']['server']['server_timezone'])) - { - $GLOBALS['egw_info']['server']['server_timezone'] = date_default_timezone_get(); - } - // make sure we have a valid server timezone set - try { - self::$server_timezone = new DateTimeZone($GLOBALS['egw_info']['server']['server_timezone']); - } - catch(Exception $e) - { - try { - self::$server_timezone = new DateTimeZone(date_default_timezone_get()); - } - catch(Exception $e) - { - self::$server_timezone = new DateTimeZone('Europe/Berlin'); - } - error_log(__METHOD__."() invalid server_timezone='{$GLOBALS['egw_info']['server']['server_timezone']}' setting now '".self::$server_timezone->getName()."'!"); - config::save_value('server_timezone',$GLOBALS['egw_info']['server']['server_timezone'] = self::$server_timezone->getName(),'phpgwapi'); - } - if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['tz'])) - { - $GLOBALS['egw_info']['user']['preferences']['common']['tz'] = $GLOBALS['egw_info']['server']['server_timezone']; - } - self::setUserPrefs($GLOBALS['egw_info']['user']['preferences']['common']['tz'], - $GLOBALS['egw_info']['user']['preferences']['common']['dateformat'], - $GLOBALS['egw_info']['user']['preferences']['common']['timeformat']); - } - - /** - * Return "beautified" timezone list: - * - no depricated timezones - * - return UTC and oceans at the end - * - if (user lang is a european language), move Europe to top - * - * @return array continent|ocean => array(tz-name => tz-label incl. current time) - */ - public static function getTimezones() - { - // prepare list of timezones from php, ignoring depricated ones and sort as follows - $tzs = array( - 'Africa' => array(), // Contients - 'America' => array(), - 'Asia' => array(), - 'Australia' => array(), - 'Europe' => array(), - 'Atlantic' => array(), // Oceans - 'Pacific' => array(), - 'Indian' => array(), - 'Antarctica' => array(), // Poles - 'Arctic' => array(), - 'UTC' => array('UTC' => 'UTC'), - ); - // no VTIMEZONE available in calendar_timezones --> do NOT return them - static $no_vtimezone = array( - 'Europe/Tiraspol', - 'America/Atka', - 'America/Buenos_Aires', - 'America/Catamarca', - 'America/Coral_Harbour', - 'America/Cordoba', - 'America/Ensenada', - 'America/Fort_Wayne', - 'America/Indianapolis', - 'America/Jujuy', - 'America/Knox_IN', - 'America/Mendoza', - 'America/Porto_Acre', - 'America/Rosario', - 'America/Virgin', - 'Asia/Ashkhabad', - 'Asia/Beijing', - 'Asia/Chungking', - 'Asia/Dacca', - 'Asia/Macao', - 'Asia/Riyadh87', - 'Asia/Riyadh88', - 'Asia/Riyadh89', - 'Asia/Tel_Aviv', - 'Asia/Thimbu', - 'Asia/Ujung_Pandang', - 'Asia/Ulan_Bator', - 'Australia/ACT', - 'Australia/Canberra', - 'Australia/LHI', - 'Australia/North', - 'Australia/NSW', - 'Australia/Queensland', - 'Australia/South', - 'Australia/Tasmania', - 'Australia/Victoria', - 'Australia/West', - 'Australia/Yancowinna', - 'Pacific/Samoa', - ); - foreach(DateTimeZone::listIdentifiers() as $name) - { - if (in_array($name,$no_vtimezone)) continue; // do NOT allow to set in EGroupware, as we have not VTIMEZONE component for it - list($continent,$rest) = explode('/',$name,2); - if (!isset($tzs[$continent])) continue; // old depricated timezones - $datetime = new egw_time('now',new DateTimeZone($name)); - $tzs[$continent][$name] = str_replace(array('_','/'),array(' ',' / '),$name)." ".$datetime->format(); - unset($datetime); - } - foreach($tzs as $continent => &$data) - { - natcasesort($data); // sort cities - } - unset($data); - - // if user lang or installed langs contain a european language --> move Europe to top of tz list - $langs = class_exists('translation') ? translation::get_installed_langs() : array(); - if (array_intersect(array($GLOBALS['egw_info']['user']['preferences']['common']['lang'])+array_keys($langs), - array('de','fr','it','nl','bg','ca','cs','da','el','es-es','et','eu','fi','hr','hu','lt','no','pl','pt','sk','sl','sv','tr','uk'))) - { - $tzs = array_merge(array('Europe' => $tzs['Europe']),$tzs); - } - return $tzs; - } - - /** - * Get user timezones (the ones user selected in his prefs), plus evtl. an extra one - * - * @param string $extra extra timezone to add, if not already included in user timezones - * @return array tzid => label - */ - public static function getUserTimezones($extra=null) - { - $tz = $GLOBALS['egw_info']['user']['preferences']['common']['tz']; - $user_tzs = explode(',',$GLOBALS['egw_info']['user']['preferences']['common']['tz_selection']); - if (count($user_tzs) <= 1) - { - $user_tzs = $tz ? array($tz) : array(); - } - if ($tz && !in_array($tz,$user_tzs)) - { - $user_tzs = array_merge(array($tz),$user_tzs); - } - if (!$user_tzs) // if we have no user timezones, eg. user set no pref --> use server default - { - $user_tzs = array($GLOBALS['egw_info']['server']['server_timezone']); - } - if ($extra && !in_array($extra,$user_tzs)) - { - $user_tzs = array_merge(array($extra),$user_tzs); - } - $user_tzs = array_combine($user_tzs,$user_tzs); - foreach($user_tzs as $name => &$label) - { - $label = str_replace(array('_','/'),array(' ',' / '),$label); - } - //_debug_array($user_tzs); - return $user_tzs; - } -} -egw_time::init(); - -/* -if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) // some tests -{ - // test timestamps/dates before 1970 - foreach(array('19690811',-3600,'-119322000') as $ts) - { - try { - echo "

egw_time::to($ts,'Y-m-d H:i:s')=".egw_time::to($ts,'Y-m-d H:i:s')."

\n"; - $et = new egw_time($ts); - echo "

egw_time($ts)->format('Y-m-d H:i:s')=".$et->format('Y-m-d H:i:s')."

\n"; - $dt = new DateTime($ts); - echo "

DateTime($ts)->format('Y-m-d H:i:s')=".$dt->format('Y-m-d H:i:s')."

\n"; - } catch(Exception $e) { - echo "

Exception: ".$e->getMessage()."

\n"; - } - } - // user time is UTC - echo "

user timezone = ".($GLOBALS['egw_info']['user']['preferences']['common']['tz'] = 'UTC').", server timezone = ".date_default_timezone_get()."

\n"; - - $time = time(); - echo "

time=$time=".date('Y-m-d H:i:s',$time)."(server) =".egw_time::server2user($time,'Y-m-d H:i:s')."(user) =".egw_time::server2user($time,'ts')."(user)=".date('Y-m-d H:i:s',egw_time::server2user($time,'ts'))."

\n"; - - echo "egw_time::to(array('full' => '20091020', 'hour' => 12, 'minute' => 0))='".egw_time::to(array('full' => '20091020', 'hour' => 12, 'minute' => 0))."'

\n"; - - $ts = egw_time::to(array('full' => '20091027', 'hour' => 10, 'minute' => 0),'ts'); - echo "

2009-10-27 10h UTC timestamp=$ts --> server time = ".egw_time::user2server($ts,'')." --> user time = ".egw_time::server2user(egw_time::user2server($ts),'')."

\n"; - - $ts = egw_time::to(array('full' => '20090627', 'hour' => 10, 'minute' => 0),'ts'); - echo "

2009-06-27 10h UTC timestamp=$ts --> server time = ".egw_time::user2server($ts,'')." --> user time = ".egw_time::server2user(egw_time::user2server($ts),'')."

\n"; -} -*/ +class egw_time extends Api\DateTime {}