Completed
Push — 16.1 ( 96a09f...9e378a )
by Nathan
36:36 queued 19:17
created

DateTime::createFromUserFormat()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 20
nc 5
nop 1
dl 0
loc 36
rs 8.439
c 0
b 0
f 0
1
<?php
2
/**
3
 * EGroupware time and timezone handling
4
 *
5
 * @package api
6
 * @link http://www.egroupware.org
7
 * @author Ralf Becker <[email protected]>
8
 * @copyright 2009-16 by [email protected]
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
namespace EGroupware\Api;
14
15
// we do not have an own implementation/extensions
16
use DateTimeZone;
17
use DateInterval;
18
19
/**
20
 * EGroupware time and timezone handling class extending PHP's DateTime
21
 *
22
 * Api\DateTime class knows 2 timezones:
23
 * 1. Api\DateTime::$user_timezone   timezone of the user, defined in his prefs $GLOBALS['egw_info']['user']['preferences']['common']['tz']
24
 * 2. Api\DateTime::$server_timezone timezone of the server, read via date_default_timezone_get()
25
 *
26
 * The class extends PHP5.2's DateTime object to:
27
 * - format date and time according to format in user prefs ($type===true: date, $type===false: time, $type==='' date+time)
28
 * - defaulting to a user timezone according to user prefs (not server timezone as DateTime!)
29
 * - deal with integer unix timestamps and DB timestamps in server-time ($type: 'ts'='integer' or 'server' timestamp in servertime)
30
 *
31
 * There are two static methods for simple conversation between server and user time:
32
 * - Api\DateTime::server2user($time,$type=null)
33
 * - Api\DateTime::user2server($time,$type=null)
34
 * (Replacing in 1.6 and previous used adding of tz_offset, which is only correct for current time)
35
 *
36
 * An other static method allows to format any time in several ways: Api\DateTime::to($time,$type) (exceed date($type,$time)).
37
 *
38
 * The constructor of Api\DateTime understand - in addition to DateTime - integer timestamps, array with values for
39
 * keys: ('year', 'month', 'day') or 'full' plus 'hour', 'minute' and optional 'second' or a DateTime object as parameter.
40
 * It defaults to user-time, not server time as DateTime!
41
 *
42
 * The constructor itself throws an \Exception in that case (to be precise it does not handle the one thrown by DateTime constructor).
43
 * Static methods server2user, user2server and to return NULL, if given time could not be parsed.
44
 *
45
 * @link http://www.php.net/manual/en/class.datetime.php
46
 * @link http://www.php.net/manual/en/class.datetimezone.php
47
 */
48
class DateTime extends \DateTime
49
{
50
	/**
51
	 * Database timestamp format: Y-m-d H:i:s
52
	 */
53
	const DATABASE = 'Y-m-d H:i:s';
54
55
	/**
56
	 * etemplate2 format for ignoring timezones in the browser
57
	 */
58
	const ET2 = 'Y-m-d\TH:i:s\Z';
59
	/**
60
	 * DateTimeZone of server, read from $GLOBALS['egw_info']['server']['server_timezone'], set by self::init()
61
	 *
62
	 * @var DateTimeZone
63
	 */
64
	static public $server_timezone;
65
66
	/**
67
	 * DateTimeZone of user, read from user prefs, set by self::init() or self::setUserPrefs()
68
	 *
69
	 * @var DateTimeZone
70
	 */
71
	static public $user_timezone;
72
73
	/**
74
	 * Time format from user prefs, set by self::setUserPrefs()
75
	 *
76
	 * @var string
77
	 */
78
	static public $user_timeformat = 'H:i';
79
80
	/**
81
	 * Date format from user prefs, set by self::setUserPrefs()
82
	 *
83
	 * @var string
84
	 */
85
	static public $user_dateformat = 'Y-m-d';
86
87
	/**
88
	 * Constructor
89
	 *
90
	 * @param int|string|array|DateTime $time ='now' integer timestamp, string with date+time, DateTime object or
91
	 * 	array with values for keys('year','month','day') or 'full' plus 'hour','minute' and optional 'second'
92
	 * @param DateTimeZone $tz =null timezone, default user time (PHP DateTime default to server time!)
93
	 * @param string &$type=null on return type of $time (optional)
94
	 * @throws Exception if $time can NOT be parsed
95
	 */
96
	public function __construct($time='now',DateTimeZone $tz=null,&$type=null)
97
	{
98
		if (is_null($tz)) $tz = self::$user_timezone;	// default user timezone
99
100
		switch(($type = gettype($time)))
101
		{
102
			case 'NULL':
103
			case 'boolean':	// depricated use in calendar for 'now'
104
				$time = 'now';
105
				$type = 'string';
106
				// fall through
107
			case 'string':
108
				if (!(is_numeric($time) && ($time > 21000000 || $time < 19000000)))
109
				{
110
					$t_str = $time;
111
					if (is_numeric($time) && strlen($time) == 8) $t_str .= 'T000000';	// 'Ymd' string used in calendar to represent a date
112
					// $time ending in a Z (Zulu or UTC time), is unterstood by DateTime class itself
113
					try {
114
						parent::__construct($t_str,$tz);
115
						break;
116
					}
117
					catch(Exception $e) {
118
						// if string is nummeric, ignore the exception and treat string as timestamp
119
						if (!is_numeric($time)) throw $e;
120
					}
121
				}
122
				$type = 'integer';
123
				// fall through for timestamps
124
			case 'double':	// 64bit integer (timestamps > 2038) are treated on 32bit systems as double
125
			case 'integer':
126
				/* ToDo: Check if PHP5.3 setTimestamp does the same, or always expects UTC timestamp
127
				if (PHP_VERSION >= 5.3)
128
				{
129
					parent::__construct('now',$tz);
130
					$datetime->setTimestamp($time);
131
				}
132
				else*/
133
				{
134
					parent::__construct(date('Y-m-d H:i:s',$time),$tz);
135
				}
136
				break;
137
138
			case 'array':
139
				parent::__construct('now',$tz);
140
				if (isset($time['Y']))	// array format used in eTemplate
141
				{
142
					$time = array(
143
						'year'   => $time['Y'],
144
						'month'  => $time['m'],
145
						'day'    => $time['d'],
146
						'hour'   => $time['H'],
147
						'minute' => $time['i'],
148
						'second' => $time['s'],
149
					);
150
				}
151
				if (!empty($time['full']) && empty($time['year']))
152
				{
153
					$time['year']  = (int)substr($time['full'],0,4);
154
					$time['month'] = (int)substr($time['full'],4,2);
155
					$time['day']   = (int)substr($time['full'],6,2);
156
				}
157
				if (isset($time['year'])) $this->setDate((int)$time['year'],(int)$time['month'],isset($time['day']) ? (int)$time['day'] : (int)$time['mday']);
158
				$this->setTime((int)$time['hour'],(int)$time['minute'],(int)$time['second']);
159
				break;
160
161
			case 'object':
162
				if ($time instanceof \DateTime)
163
				{
164
					parent::__construct($time->format('Y-m-d H:i:s'),$time->getTimezone());
165
					$this->setTimezone($tz);
166
					break;
167
				}
168
				// fall through
169
			default:
170
				throw new Exception\AssertionFailed("Not implemented for type ($type)$time!");
171
		}
172
	}
173
174
	/**
175
	 * Like DateTime::add, but additional allow to use a string run through DateInterval::createFromDateString
176
	 *
177
	 * @param DateInterval|string $interval eg. '1 day', '-2 weeks'
178
	 */
179
	public function add($interval)
180
	{
181
		if (is_string($interval)) $interval = DateInterval::createFromDateString($interval);
182
183
		parent::add($interval);
184
	}
185
186
	/**
187
	 * Set date to beginning of the week taking into account calendar weekdaystarts preference
188
	 */
189
	public function setWeekstart()
190
	{
191
		$wday = (int) $this->format('w'); // 0=sun, ..., 6=sat
192
		switch($GLOBALS['egw_info']['user']['preferences']['calendar']['weekdaystarts'])
193
		{
194
			case 'Sunday':
195
				$wstart = -$wday;
196
				break;
197
			case 'Saturday':
198
				$wstart =  -(6-$wday);
199
				break;
200
			case 'Moday':
201
			default:
202
				$wstart = -($wday ? $wday-1 : 6);
203
				break;
204
		}
205
		if ($wstart) $this->add($wstart.'days');
206
	}
207
208
	/**
209
	 * return SQL implementing filtering by date
210
	 *
211
	 * @param string $name
212
	 * @param int &$start
213
	 * @param int &$end
214
	 * @param string $column name of timestamp column to use in returned sql
215
	 * @param array $filters $name => list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) pairs with offsets
216
	 * @return string
217
	 */
218
	public static function sql_filter($name, &$start, &$end, $column, array $filters=array())
219
	{
220
		if ($name == 'custom' && $start)
221
		{
222
			$start = new DateTime($start);
223
			$start->setTime(0, 0, 0);
224
225
			if ($end)
226
			{
227
				$end = new DateTime($end);
228
				$end->setTime(0, 0, 0);
229
				$end->add('+1day');
230
			}
231
		}
232
		else
233
		{
234
			if (!isset($filters[$name]))
235
			{
236
				return '1=1';
237
			}
238
			$start = new DateTime('now');
239
			$start->setTime(0, 0, 0);
240
			$end   = new DateTime('now');
241
			$end->setTime(0, 0, 0);
242
243
			$year  = (int) $start->format('Y');
244
			$month = (int) $start->format('m');
245
246
			list($syear,$smonth,$sday,$sweek,$eyear,$emonth,$eday,$eweek) = $filters[$name];
247
248
			// Handle quarters
249
			if(stripos($name, 'quarter') !== false)
250
			{
251
				$start->setDate($year, ((int)floor(($smonth+$month) / 3.1)) * 3 + 1, 1);
252
				$end->setDate($year, ((int)floor(($emonth+$month) / 3.1)+1) * 3 + 1, 1);
253
			}
254
			elseif ($syear || $eyear)
255
			{
256
				$start->setDate($year+$syear, 1, 1);
257
				$end->setDate($year+$eyear, 1, 1);
258
			}
259
			elseif ($smonth || $emonth)
260
			{
261
				$start->setDate($year, $month+$smonth, 1);
262
				$end->setDate($year, $month+$emonth, 1);
263
			}
264
			elseif ($sday || $eday)
265
			{
266
				if ($sday) $start->add($sday.'days');
267
				if ($eday) $end->add($eday.'days');
268
			}
269
			elseif ($sweek || $eweek)
270
			{
271
				$start->setWeekstart();
272
				if ($sweek) $start->add($sweek.'weeks');
273
				$end->setWeekstart();
274
				if ($eweek) $end->add($eweek.'weeks');
275
			}
276
		}
277
		// convert start + end from user to servertime for the filter
278
		$sql = '('.DateTime::user2server($start, 'ts').' <= '.$column;
279
		if($end)
280
		{
281
			$sql .=' AND '.$column.' < '.DateTime::user2server($end, 'ts');
282
283
			// returned timestamps: $end is an inclusive date, eg. for today it's equal to start!
284
			$end->add('-1day');
285
			$end = $end->format('ts');
286
		}
287
		$sql .= ')';
288
		//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'");
289
290
		$start = $start->format('ts');
291
292
		return $sql;
293
	}
294
295
	/**
296
	 * Set user timezone, according to user prefs: converts current time to user time
297
	 *
298
	 * Does nothing if self::$user_timezone is current timezone!
299
	 */
300
	public function setUser()
301
	{
302
		$this->setTimezone(self::$user_timezone);
303
	}
304
305
	/**
306
	 * Set server timezone: converts current time to server time
307
	 *
308
	 * Does nothing if self::$server_timezone is current timezone!
309
	 */
310
	public function setServer()
311
	{
312
		$this->setTimezone(self::$server_timezone);
313
	}
314
315
	/**
316
	 * Format DateTime object as a specific type or string
317
	 *
318
	 * @param string $type ='' 'integer'|'ts'=timestamp, 'server'=timestamp in servertime, 'string'='Y-m-d H:i:s', 'object'=DateTime,
319
	 * 		'array'=array with values for keys ('year','month','day','hour','minute','second','full','raw') or string with format
320
	 * 		true = date only, false = time only as in user prefs, '' = date+time as in user prefs
321
	 * @return int|string|array|datetime see $type
322
	 */
323
	public function format($type='')
324
	{
325
		switch((string)$type)
326
		{
327
			case '':	// empty string:  date and time as in user prefs
328
			//case '':	// boolean false: time as in user prefs
329
			case '1':	// boolean true:  date as in user prefs
330
				if (is_bool($type))
331
				{
332
					$type = $type ? self::$user_dateformat : self::$user_timeformat;
333
				}
334
				else
335
				{
336
					$type = self::$user_dateformat.', '.self::$user_timeformat;
337
				}
338
				break;
339
340
			case 'string':
341
				$type = self::DATABASE;
342
				break;
343
344
			case 'server':	// timestamp in servertime
345
				$this->setServer();
346
				// fall through
347
			case 'integer':
348
			case 'ts':
349
				// ToDo: Check if PHP5.3 getTimestamp does the same, or always returns UTC timestamp
350
				return mktime(parent::format('H'),parent::format('i'),parent::format('s'),parent::format('m'),parent::format('d'),parent::format('Y'));
351
352
			case 'object':
353
			case 'datetime':
354
			case 'egw_time':
355
			case 'DateTime':
356
				return clone($this);
357
358
			case 'array':
359
				$arr = array(
360
					'year'   => (int)parent::format('Y'),
361
					'month'  => (int)parent::format('m'),
362
					'day'    => (int)parent::format('d'),
363
					'hour'   => (int)parent::format('H'),
364
					'minute' => (int)parent::format('i'),
365
					'second' => (int)parent::format('s'),
366
					'full'   => parent::format('Ymd'),
367
				);
368
				$arr['raw'] = mktime($arr['hour'],$arr['minute'],$arr['second'],$arr['month'],$arr['day'],$arr['year']);
369
				return $arr;
370
371
			case 'date_array':	// array with short keys used by date: Y, m, d, H, i, s (used in eTemplate)
372
				return array(
373
					'Y' => (int)parent::format('Y'),
374
					'm' => (int)parent::format('m'),
375
					'd' => (int)parent::format('d'),
376
					'H' => (int)parent::format('H'),
377
					'i' => (int)parent::format('i'),
378
					's' => (int)parent::format('s'),
379
				);
380
		}
381
		// default $type contains string with format
382
		return parent::format($type);
383
	}
384
385
	/**
386
	 * Cast object to string
387
	 *
388
	 * @return string eg. "Wednesday, 2009-11-11 11:11:11 (Europe/Berlin)"
389
	 */
390
	public function __toString()
391
	{
392
		return $this->format('l, '.self::DATABASE).' ('.$this->getTimezone()->getName().')';
393
	}
394
395
	/**
396
	 * Convert a server time into a user time
397
	 *
398
	 * @param int|string|array|DateTime $time
399
	 * @param string $type =null type or return-value, default (null) same as $time
400
	 * @return int|string|array|datetime null if time could not be parsed
401
	 */
402 View Code Duplication
	public static function server2user($time,$type=null)
403
	{
404
		$typeof='DateTime';
405
		if (!($time instanceof DateTime))
406
		{
407
			try
408
			{
409
				$time = new DateTime($time, self::$server_timezone, $typeof);
410
			}
411
			catch(Exception $e)
412
			{
413
				unset($e);
414
				return null;	// time could not be parsed
415
			}
416
		}
417
		$time->setUser();
418
419
		if (is_null($type)) $type = $typeof;
420
421
		//echo "<p>".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."</p>\n";
422
		return $time->format($type);
423
	}
424
425
	/**
426
	 * Convert a user time into a server time
427
	 *
428
	 * @param int|string|array|datetime $time
429
	 * @param string $type =null type or return-value, default (null) same as $time
430
	 * @return int|string|array|datetime null if time could not be parsed
431
	 */
432 View Code Duplication
	public static function user2server($time,$type=null)
433
	{
434
		$typeof='DateTime';
435
		if (!($time instanceof DateTime))
436
		{
437
			try
438
			{
439
				$time = new DateTime($time,self::$user_timezone,$typeof);
440
			}
441
			catch(Exception $e)
442
			{
443
				unset($e);
444
				return null;	// time could not be parsed
445
			}
446
		}
447
		$time->setServer();
448
449
		if (is_null($type)) $type = $typeof;
450
451
		//echo "<p>".__METHOD__."($time,$type) = ".print_r($format->format($type),true)."</p>\n";
452
		return $time->format($type);
453
	}
454
455
	/**
456
	 * Convert time to a specific format or string, static version of DateTime::format()
457
	 *
458
	 * @param int|string|array|DateTime $time ='now' see constructor
459
	 * @param string $type ='' 'integer'|'ts'=timestamp, 'server'=timestamp in servertime, 'string'='Y-m-d H:i:s', 'object'=DateTime,
460
	 * 		'array'=array with values for keys ('year','month','day','hour','minute','second','full','raw') or string with format
461
	 * 		true = date only, false = time only as in user prefs, '' = date+time as in user prefs
462
	 * @return int|string|array|datetime see $type, null if time could not be parsed
463
	 */
464
	public static function to($time='now',$type='')
465
	{
466
		if (!($time instanceof DateTime))
467
		{
468
			try
469
			{
470
				// Try user format first
471
				$time = static::createFromUserFormat($time);
472
			}
473
			catch(\Exception $e)
474
			{
475
				unset($e);
476
				return null;	// time could not be parsed
477
			}
478
		}
479
		return $time->format($type);
480
	}
481
482
	/**
483
	 * Some user formats are conflicting and cannot be reliably parsed by the
484
	 * normal means.  Here we agressively try various date formats (based on
485
	 * user's date preference) to coerce a troublesome date into a DateTime.
486
	 *
487
	 * ex: 07/08/2018 could be DD/MM/YYYY or MM/DD/YYYY
488
	 *
489
	 * Rather than trust DateTime to guess right based on its settings, we use
490
	 * the user's date preference to resolve the ambiguity.
491
	 *
492
	 * @param string $time
493
	 * @return DateTime or null
494
	 */
495
	public static function createFromUserFormat($time)
496
	{
497
		$date = null;
498
499
		// If numeric, just let normal constructor do it
500
		if(is_numeric($time))
501
		{
502
			return new DateTime($time);
503
		}
504
505
		// Various date formats in decreasing preference
506
		$formats = array(
507
			'!'.static::$user_dateformat . ' ' .static::$user_timeformat.':s',
508
			'!'.static::$user_dateformat . '*' .static::$user_timeformat.':s',
509
			'!'.static::$user_dateformat . '* ' .static::$user_timeformat,
510
			'!'.static::$user_dateformat . '*',
511
			'!'.static::$user_dateformat,
512
			'!Y-m-d\TH:i:s'
513
		);
514
		// Try the different formats, stop when one works
515
		foreach($formats as $f)
516
		{
517
			try {
518
				$date = static::createFromFormat(
519
					$f,
520
					$time,
521
					static::$user_timezone
522
				);
523
				if($date) break;
524
			} catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
525
526
			}
527
		}
528
		// Need correct class, createFromFormat() gives parent
529
		return new DateTime($date ? $date : $time);
0 ignored issues
show
Bug introduced by
It seems like $date ? $date : $time can also be of type object<DateTime>; however, EGroupware\Api\DateTime::__construct() does only seem to accept string|integer|array|obj...Groupware\Api\DateTime>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
530
	}
531
532
	/**
533
	 * Setter for user timezone, should be called after reading user preferences
534
	 *
535
	 * @param string $tz timezone, eg. 'Europe/Berlin' or 'UTC'
536
	 * @param string $dateformat ='' eg. 'Y-m-d' or 'd.m.Y'
537
	 * @param string|int $timeformat ='' integer 12, 24, or format string eg. 'H:i'
538
	 * @return DateTimeZone
539
	 */
540
	public static function setUserPrefs($tz,$dateformat='',$timeformat='')
541
	{
542
		//echo "<p>".__METHOD__."('$tz','$dateformat','$timeformat') ".function_backtrace()."</p>\n";
543
		if (!empty($dateformat)) self::$user_dateformat = $dateformat;
544
545
		switch($timeformat)
546
		{
547
			case '':
548
				break;
549
			case '24':
550
				self::$user_timeformat = 'H:i';
551
				break;
552
			case '12':
553
				self::$user_timeformat = 'h:i a';
554
				break;
555
			default:
556
				self::$user_timeformat = $timeformat;
0 ignored issues
show
Documentation Bug introduced by
It seems like $timeformat can also be of type integer. However, the property $user_timeformat is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
557
				break;
558
		}
559
		try {
560
			self::$user_timezone = new DateTimeZone($tz);
561
		}
562
		catch(\Exception $e)
563
		{
564
			unset($e);
565
			// silently use server timezone, as we have no means to report the wrong timezone to the user from this class
566
			self::$user_timezone = clone(self::$server_timezone);
567
		}
568
		return self::$user_timezone;
569
	}
570
571
	/**
572
	 * Get offset in seconds between user and server time at given time $time
573
	 *
574
	 * Compatibility method for old code. It is only valid for the given time, because of possible daylight saving changes!
575
	 *
576
	 * @param int|string|DateTime $time ='now'
577
	 * @return int difference in seconds between user and server time (for the given time!)
578
	 */
579
	public static function tz_offset_s($time='now')
580
	{
581
		if (!($time instanceof DateTime)) $time = new DateTime($time);
582
583
		return self::$user_timezone->getOffset($time) - self::$server_timezone->getOffset($time);
584
	}
585
586
	/**
587
	 * Init static variables, reading user prefs
588
	 */
589
	public static function init()
590
	{
591
		// if no server timezone set, use date_default_timezone_get() to determine it
592 View Code Duplication
		if (empty($GLOBALS['egw_info']['server']['server_timezone']))
593
		{
594
			$GLOBALS['egw_info']['server']['server_timezone'] = date_default_timezone_get();
595
		}
596
		// make sure we have a valid server timezone set
597
		try {
598
			self::$server_timezone = new DateTimeZone($GLOBALS['egw_info']['server']['server_timezone']);
599
		}
600
		catch(\Exception $e)
601
		{
602
			try {
603
				self::$server_timezone = new DateTimeZone(date_default_timezone_get());
604
			}
605
			catch(\Exception $e)
606
			{
607
				self::$server_timezone = new DateTimeZone('Europe/Berlin');
608
			}
609
			error_log(__METHOD__."() invalid server_timezone='{$GLOBALS['egw_info']['server']['server_timezone']}' setting now '".self::$server_timezone->getName()."'!");
610
			Config::save_value('server_timezone',$GLOBALS['egw_info']['server']['server_timezone'] = self::$server_timezone->getName(),'phpgwapi');
611
		}
612
		if (!isset($GLOBALS['egw_info']['user']['preferences']['common']['tz']))
613
		{
614
			$GLOBALS['egw_info']['user']['preferences']['common']['tz'] = $GLOBALS['egw_info']['server']['server_timezone'];
615
		}
616
		self::setUserPrefs($GLOBALS['egw_info']['user']['preferences']['common']['tz'],
617
			$GLOBALS['egw_info']['user']['preferences']['common']['dateformat'],
618
			$GLOBALS['egw_info']['user']['preferences']['common']['timeformat']);
619
	}
620
621
	/**
622
	 * Return "beautified" timezone list:
623
	 * - no depricated timezones
624
	 * - return UTC and oceans at the end
625
	 * - if (user lang is a european language), move Europe to top
626
	 *
627
	 * @return array continent|ocean => array(tz-name => tz-label incl. current time)
628
	 */
629
	public static function getTimezones()
630
	{
631
		// prepare list of timezones from php, ignoring depricated ones and sort as follows
632
		$tzs = array(
633
			'Africa'     => array(),	// Contients
634
			'America'    => array(),
635
			'Asia'       => array(),
636
			'Australia'  => array(),
637
			'Europe'     => array(),
638
			'Atlantic'   => array(),	// Oceans
639
			'Pacific'    => array(),
640
			'Indian'     => array(),
641
			'Antarctica' => array(),	// Poles
642
			'Arctic'     => array(),
643
			'UTC'        => array('UTC' => 'UTC'),
644
		);
645
		// no VTIMEZONE available in calendar_timezones --> do NOT return them
646
		static $no_vtimezone = array(
647
			'Europe/Tiraspol',
648
			'America/Atka',
649
			'America/Buenos_Aires',
650
			'America/Catamarca',
651
			'America/Coral_Harbour',
652
			'America/Cordoba',
653
			'America/Ensenada',
654
			'America/Fort_Wayne',
655
			'America/Indianapolis',
656
			'America/Jujuy',
657
			'America/Knox_IN',
658
			'America/Mendoza',
659
			'America/Porto_Acre',
660
			'America/Rosario',
661
			'America/Virgin',
662
			'Asia/Ashkhabad',
663
			'Asia/Beijing',
664
			'Asia/Chungking',
665
			'Asia/Dacca',
666
			'Asia/Macao',
667
			'Asia/Riyadh87',
668
			'Asia/Riyadh88',
669
			'Asia/Riyadh89',
670
			'Asia/Tel_Aviv',
671
			'Asia/Thimbu',
672
			'Asia/Ujung_Pandang',
673
			'Asia/Ulan_Bator',
674
			'Australia/ACT',
675
			'Australia/Canberra',
676
			'Australia/LHI',
677
			'Australia/North',
678
			'Australia/NSW',
679
			'Australia/Queensland',
680
			'Australia/South',
681
			'Australia/Tasmania',
682
			'Australia/Victoria',
683
			'Australia/West',
684
			'Australia/Yancowinna',
685
			'Pacific/Samoa',
686
		);
687
		foreach(DateTimeZone::listIdentifiers() as $name)
688
		{
689
			if (in_array($name,$no_vtimezone)) continue;	// do NOT allow to set in EGroupware, as we have not VTIMEZONE component for it
690
			list($continent) = explode('/',$name,2);
691
			if (!isset($tzs[$continent])) continue;	// old depricated timezones
692
			$datetime = new DateTime('now',new DateTimeZone($name));
693
			$tzs[$continent][$name] = str_replace(array('_','/'),array(' ',' / '),$name)."  ".$datetime->format();
694
			unset($datetime);
695
		}
696
		foreach($tzs as $continent => &$data)
697
		{
698
			natcasesort($data);	// sort cities
699
		}
700
		unset($data);
701
702
		// if user lang or installed langs contain a european language --> move Europe to top of tz list
703
		$langs = class_exists('EGroupware\\Api\\Translation') ? Translation::get_installed_langs() : array();
704
		if (array_intersect(array($GLOBALS['egw_info']['user']['preferences']['common']['lang'])+array_keys($langs),
705
			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')))
706
		{
707
			$tzs = array_merge(array('Europe' => $tzs['Europe']),$tzs);
708
		}
709
		return $tzs;
710
	}
711
712
	/**
713
	 * Get user timezones (the ones user selected in his prefs), plus evtl. an extra one
714
	 *
715
	 * @param string $extra extra timezone to add, if not already included in user timezones
716
	 * @return array tzid => label
717
	 */
718
	public static function getUserTimezones($extra=null)
719
	{
720
		$tz = $GLOBALS['egw_info']['user']['preferences']['common']['tz'];
721
		$user_tzs = explode(',',$GLOBALS['egw_info']['user']['preferences']['common']['tz_selection']);
722
		if (count($user_tzs) <= 1)
723
		{
724
			$user_tzs = $tz ? array($tz) : array();
725
		}
726
		if ($tz && !in_array($tz,$user_tzs))
727
		{
728
			$user_tzs = array_merge(array($tz),$user_tzs);
729
		}
730
		if (!$user_tzs)	// if we have no user timezones, eg. user set no pref --> use server default
731
		{
732
			$user_tzs = array($GLOBALS['egw_info']['server']['server_timezone']);
733
		}
734
		if ($extra && !in_array($extra,$user_tzs))
735
		{
736
			$user_tzs = array_merge(array($extra),$user_tzs);
737
		}
738
		$ret_user_tzs = array_combine($user_tzs,$user_tzs);
739
		foreach($ret_user_tzs as &$label)
740
		{
741
			$label = str_replace(array('_','/'),array(' ',' / '),$label);
742
		}
743
		return $ret_user_tzs;
744
	}
745
}
746
DateTime::init();
747
748
/*
749
if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__)	// some tests
750
{
751
	// test timestamps/dates before 1970
752
	foreach(array('19690811',-3600,'-119322000') as $ts)
753
	{
754
		try {
755
			echo "<p>DateTime::to($ts,'Y-m-d H:i:s')=".DateTime::to($ts,'Y-m-d H:i:s')."</p>\n";
756
			$et = new DateTime($ts);
757
			echo "<p>DateTime($ts)->format('Y-m-d H:i:s')=".$et->format('Y-m-d H:i:s')."</p>\n";
758
			$dt = new DateTime($ts);
759
			echo "<p>DateTime($ts)->format('Y-m-d H:i:s')=".$dt->format('Y-m-d H:i:s')."</p>\n";
760
		} catch(\Exception $e) {
761
			echo "<p><b>Exception</b>: ".$e->getMessage()."</p>\n";
762
		}
763
	}
764
	// user time is UTC
765
	echo "<p>user timezone = ".($GLOBALS['egw_info']['user']['preferences']['common']['tz'] = 'UTC').", server timezone = ".date_default_timezone_get()."</p>\n";
766
767
	$time = time();
768
	echo "<p>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'))."</p>\n";
769
770
	echo "DateTime::to(array('full' => '20091020', 'hour' => 12, 'minute' => 0))='".DateTime::to(array('full' => '20091020', 'hour' => 12, 'minute' => 0))."'</p>\n";
771
772
	$ts = DateTime::to(array('full' => '20091027', 'hour' => 10, 'minute' => 0),'ts');
773
	echo "<p>2009-10-27 10h UTC timestamp=$ts --> server time = ".DateTime::user2server($ts,'')." --> user time = ".DateTime::server2user(DateTime::user2server($ts),'')."</p>\n";
774
775
	$ts = DateTime::to(array('full' => '20090627', 'hour' => 10, 'minute' => 0),'ts');
776
	echo "<p>2009-06-27 10h UTC timestamp=$ts --> server time = ".DateTime::user2server($ts,'')." --> user time = ".DateTime::server2user(DateTime::user2server($ts),'')."</p>\n";
777
}
778
*/
779