2009-10-07 15:29:06 +02:00
< ? php
/**
* EGroupware time and timezone handling
*
* @ package api
* @ link http :// www . egroupware . org
* @ author Ralf Becker < RalfBecker @ outdoor - training . de >
* @ copyright 2009 by RalfBecker @ outdoor - training . de
* @ license http :// opensource . org / licenses / gpl - license . php GPL - GNU General Public License
* @ version $Id $
*/
/**
* 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 !
*
* @ link http :// www . php . net / manual / en / class . datetime . php
* @ link http :// www . php . net / manual / en / class . datetimezone . php
*/
class egw_time extends DateTime
{
2009-10-08 18:14:18 +02:00
/**
* Database timestamp format : Y - m - d H : i : s
*/
const DATABASE = 'Y-m-d H:i:s' ;
2009-10-07 15:29:06 +02:00
/**
* DateTimeZone of server , read via date_default_timezone_get (), set by self :: init ()
*
* @ var DateTimeZone
*/
static public $server_timezone ;
/**
2009-10-08 18:14:18 +02:00
* DateTimeZone of user , read from user prefs , set by self :: init () or self :: setUserPrefs ()
2009-10-07 15:29:06 +02:00
*
* @ var DateTimeZone
*/
static public $user_timezone ;
/**
2009-10-08 18:14:18 +02:00
* Time format from user prefs , set by self :: setUserPrefs ()
2009-10-07 15:29:06 +02:00
*
* @ var string
*/
2009-10-08 18:14:18 +02:00
static public $user_timeformat = 'H:i' ;
2009-10-07 15:29:06 +02:00
/**
2009-10-08 18:14:18 +02:00
* Date format from user prefs , set by self :: setUserPrefs ()
2009-10-07 15:29:06 +02:00
*
* @ var string
*/
2009-10-08 18:14:18 +02:00
static public $user_dateformat = 'Y-m-d' ;
2009-10-07 15:29:06 +02:00
/**
* 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 )
* @ return egw_time
*/
public function __construct ( $time = 'now' , DateTimeZone $tz = null , & $type = null )
{
2009-10-08 18:14:18 +02:00
if ( is_null ( $tz )) $tz = self :: $user_timezone ; // default user timezone
2009-10-07 15:29:06 +02:00
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 ))
{
if ( is_numeric ( $time ) && strlen ( $time ) == 8 ) $time .= '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
parent :: __construct ( $time , $tz );
break ;
}
$type = 'integer' ;
// fall through for timestamps
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 );
2009-10-08 18:14:18 +02:00
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' ],
);
}
2009-10-07 15:29:06 +02:00
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 ( is_a ( $time , '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 ! " );
}
}
/**
* 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 ))
{
2009-10-08 18:14:18 +02:00
$type = $type ? self :: $user_dateformat : self :: $user_timeformat ;
2009-10-07 15:29:06 +02:00
}
else
{
2009-10-08 18:14:18 +02:00
$type = self :: $user_dateformat . ', ' . self :: $user_timeformat ;
2009-10-07 15:29:06 +02:00
}
2009-10-08 18:14:18 +02:00
break ;
2009-10-07 15:29:06 +02:00
case 'string' :
2009-10-08 18:14:18 +02:00
$type = self :: DATABASE ;
break ;
2009-10-07 15:29:06 +02:00
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' :
2009-10-08 18:14:18 +02:00
return clone ( $this );
2009-10-07 15:29:06 +02:00
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 ;
2009-10-08 18:14:18 +02:00
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' ),
);
2009-10-07 15:29:06 +02:00
}
// default $type contains string with format
return parent :: format ( $type );
}
2009-10-08 18:14:18 +02:00
/**
* Cast object to a string
*
* @ return string
*/
public function __toString ()
{
$tz = $this -> getTimezone ();
if ( ! $tz )
{
ob_start ();
debug_print_backtrace ();
error_log ( ob_get_clean ());
}
return $this -> format ( self :: DATABASE );
}
2009-10-07 15:29:06 +02:00
/**
* 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
*/
public static function server2user ( $time , $type = null )
{
if ( ! is_a ( $time , $typeof = 'egw_time' )) $time = new egw_time ( $time , self :: $server_timezone , $typeof );
$time -> setUser ();
if ( is_null ( $type )) $type = $typeof ;
2009-10-08 18:14:18 +02:00
//echo "<p>".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."</p>\n";
2009-10-07 15:29:06 +02:00
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
*/
public static function user2server ( $time , $type = null )
{
if ( ! is_a ( $time , $typeof = 'egw_time' )) $time = new egw_time ( $time , self :: $user_timezone , $typeof );
$time -> setServer ();
if ( is_null ( $type )) $type = $typeof ;
2009-10-08 18:14:18 +02:00
//echo "<p>".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."</p>\n";
2009-10-07 15:29:06 +02:00
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
*/
public static function to ( $time = 'now' , $type = '' )
{
if ( ! is_a ( $time , 'egw_time' )) $time = new egw_time ( $time );
return $time -> format ( $type );
}
2009-10-08 18:14:18 +02:00
/**
* Setter for user timezone , should be called after reading user preferences
*
* @ param string $tz timezone , eg . 'Europe/Berlin' or 'UTC'
2009-10-12 11:41:46 +02:00
* @ param string $dateformat = '' eg . 'Y-m-d' or 'd.m.Y'
* @ param string | int $timeformat = '' integer 12 , 24 , or format string eg . 'H:i'
2009-10-08 18:14:18 +02:00
* @ throws egw_exception_wrong_userinput if invalid $tz parameter
* @ return DateTimeZone
*/
2009-10-12 11:41:46 +02:00
public static function setUserPrefs ( $tz , $dateformat = '' , $timeformat = '' )
2009-10-08 18:14:18 +02:00
{
2009-10-12 11:41:46 +02:00
//echo "<p>".__METHOD__."('$tz','$dateformat','$timeformat') ".function_backtrace()."</p>\n";
2009-10-08 18:14:18 +02:00
if ( ! empty ( $dateformat )) self :: $user_dateformat = $dateformat ;
switch ( $timeformat )
{
case '' :
2009-10-12 11:41:46 +02:00
break ;
case '24' :
2009-10-08 18:14:18 +02:00
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 )
{
throw new egw_exception_wrong_userinput ( lang ( 'You need to %1set your timezone preference%2.' , '<a href="' . egw :: link ( '/index.php' , array (
'menuaction' => 'preferences.uisettings.index' ,
'appname' => 'preferences' )) . '">' , '</a>' ));
}
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 ( ! is_a ( $time , 'DateTime' )) $time = new egw_time ( $time );
return egw_time :: $user_timezone -> getOffset ( $time ) - egw_time :: $server_timezone -> getOffset ( $time );
}
2009-10-07 15:29:06 +02:00
/**
* Init static variables , reading user prefs
*/
2009-10-08 18:14:18 +02:00
public static function init ()
2009-10-07 15:29:06 +02:00
{
2009-10-08 18:14:18 +02:00
self :: $server_timezone = new DateTimeZone ( date_default_timezone_get ());
if ( isset ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'tz' ]))
2009-10-07 15:29:06 +02:00
{
2009-10-08 18:14:18 +02:00
self :: setUserPrefs ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'tz' ],
$GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'dateformat' ],
$GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'timeformat' ]);
2009-10-07 15:29:06 +02:00
}
2009-10-08 18:14:18 +02:00
else
2009-10-07 15:29:06 +02:00
{
2009-10-08 18:14:18 +02:00
self :: $user_timezone = clone ( self :: $server_timezone );
2009-10-07 15:29:06 +02:00
}
}
2009-10-12 11:41:46 +02:00
/**
* 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' ),
);
foreach ( DateTimeZone :: listIdentifiers () as $name )
{
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 = $GLOBALS [ 'egw' ] -> translation -> get_installed_langs ();
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 ;
}
2009-10-07 15:29:06 +02:00
}
2009-10-08 18:14:18 +02:00
egw_time :: init ();
2009-10-07 15:29:06 +02:00
/*
if ( isset ( $_SERVER [ 'SCRIPT_FILENAME' ]) && $_SERVER [ 'SCRIPT_FILENAME' ] == __FILE__ ) // some tests
{
// user time is UTC
echo " <p>user timezone = " . ( $GLOBALS [ 'egw_info' ][ 'user' ][ 'preferences' ][ 'common' ][ 'tz' ] = 'UTC' ) . " , server timezone = " . date_default_timezone_get () . " </p> \n " ;
$time = time ();
echo " <p>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' )) . " </p> \n " ;
echo " egw_time::to(array('full' => '20091020', 'hour' => 12, 'minute' => 0))=' " . egw_time :: to ( array ( 'full' => '20091020' , 'hour' => 12 , 'minute' => 0 )) . " '</p> \n " ;
$ts = egw_time :: to ( array ( 'full' => '20091027' , 'hour' => 10 , 'minute' => 0 ), 'ts' );
echo " <p>2009-10-27 10h UTC timestamp= $ts --> server time = " . egw_time :: user2server ( $ts , '' ) . " --> user time = " . egw_time :: server2user ( egw_time :: user2server ( $ts ), '' ) . " </p> \n " ;
$ts = egw_time :: to ( array ( 'full' => '20090627' , 'hour' => 10 , 'minute' => 0 ), 'ts' );
echo " <p>2009-06-27 10h UTC timestamp= $ts --> server time = " . egw_time :: user2server ( $ts , '' ) . " --> user time = " . egw_time :: server2user ( egw_time :: user2server ( $ts ), '' ) . " </p> \n " ;
}
*/