|
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) { |
|
|
|
|
|
|
525
|
|
|
|
|
526
|
|
|
} |
|
527
|
|
|
} |
|
528
|
|
|
// Need correct class, createFromFormat() gives parent |
|
529
|
|
|
return new DateTime($date ? $date : $time); |
|
|
|
|
|
|
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; |
|
|
|
|
|
|
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
|
|
|
|