1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SMW\DataValues\Time; |
4
|
|
|
|
5
|
|
|
use DateTimeZone; |
6
|
|
|
use DateInterval; |
7
|
|
|
use DateTime; |
8
|
|
|
|
9
|
|
|
/** |
10
|
|
|
* @private |
11
|
|
|
* |
12
|
|
|
* @license GNU GPL v2+ |
13
|
|
|
* @since 2.5 |
14
|
|
|
* |
15
|
|
|
* @author mwjames |
16
|
|
|
*/ |
17
|
|
|
class Timezone { |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* A new TZ is expected to be added at the end of the list without changing |
21
|
|
|
* existing ID's (those are used as internal serialization identifier). |
22
|
|
|
* |
23
|
|
|
* The associated offsets are in hours or fractions of hours. |
24
|
|
|
* |
25
|
|
|
* 'FOO' => array( ID, OffsetInSeconds, isMilitary ) |
26
|
|
|
* |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
private static $shortList = array( |
30
|
|
|
"UTC" => array( 0, 0, false ), |
31
|
|
|
"Z" => array( 1, 0, true ), |
32
|
|
|
"A" => array( 2, 3600, true ), |
33
|
|
|
"ACDT" => array( 3, 37800, false ), |
34
|
|
|
"ACST" => array( 4, 34200, false ), |
35
|
|
|
"ADT" => array( 5, -10800, false ), |
36
|
|
|
"AEDT" => array( 6, 39600, false ), |
37
|
|
|
"AEST" => array( 7, 36000, false ), |
38
|
|
|
"AKDT" => array( 8, -28800, false ), |
39
|
|
|
"AKST" => array( 9, -32400, false ), |
40
|
|
|
"AST" => array( 10, -14400, false ), |
41
|
|
|
"AWDT" => array( 11, 32400, false ), |
42
|
|
|
"AWST" => array( 12, 28800, false ), |
43
|
|
|
"B" => array( 13, 7200, true ), |
44
|
|
|
"BST" => array( 14, 3600, false ), |
45
|
|
|
"C" => array( 15, 10800, true ), |
46
|
|
|
"CDT" => array( 16, -18000, false ), |
47
|
|
|
"CEDT" => array( 17, 7200, false ), |
48
|
|
|
"CEST" => array( 18, 7200, false ), |
49
|
|
|
"CET" => array( 19, 3600, false ), |
50
|
|
|
"CST" => array( 20, -21600, false ), |
51
|
|
|
"CXT" => array( 21, 25200, false ), |
52
|
|
|
"D" => array( 22, 14400, true ), |
53
|
|
|
"E" => array( 23, 18000, true ), |
54
|
|
|
"EDT" => array( 24, -14400, false ), |
55
|
|
|
"EEDT" => array( 25, 10800, false ), |
56
|
|
|
"EEST" => array( 26, 10800, false ), |
57
|
|
|
"EET" => array( 27, 7200, false ), |
58
|
|
|
"EST" => array( 28, -18000, false ), |
59
|
|
|
"F" => array( 29, 21600, true ), |
60
|
|
|
"G" => array( 30, 25200, true ), |
61
|
|
|
"GMT" => array( 31, 0, false ), |
62
|
|
|
"H" => array( 32, 28800, true ), |
63
|
|
|
"HAA" => array( 33, -10800, false ), |
64
|
|
|
"HAC" => array( 34, -18000, false ), |
65
|
|
|
"HADT" => array( 35, -32400, false ), |
66
|
|
|
"HAE" => array( 36, -14400, false ), |
67
|
|
|
"HAP" => array( 37, -25200, false ), |
68
|
|
|
"HAR" => array( 38, -21600, false ), |
69
|
|
|
"HAST" => array( 39, -36000, false ), |
70
|
|
|
"HAT" => array( 40, -9000, false ), |
71
|
|
|
"HAY" => array( 41, -28800, false ), |
72
|
|
|
"HNA" => array( 42, -14400, false ), |
73
|
|
|
"HNC" => array( 43, -21600, false ), |
74
|
|
|
"HNE" => array( 44, -18000, false ), |
75
|
|
|
"HNP" => array( 45, -28800, false ), |
76
|
|
|
"HNR" => array( 46, -25200, false ), |
77
|
|
|
"HNT" => array( 47, -12600, false ), |
78
|
|
|
"HNY" => array( 48, -32400, false ), |
79
|
|
|
"I" => array( 49, 32400, true ), |
80
|
|
|
"IST" => array( 50, 3600, false ), |
81
|
|
|
"K" => array( 51, 36000, true ), |
82
|
|
|
"L" => array( 52, 39600, true ), |
83
|
|
|
"M" => array( 53, 43200, true ), |
84
|
|
|
"MDT" => array( 54, -21600, false ), |
85
|
|
|
"MESZ" => array( 55, 7200, false ), |
86
|
|
|
"MEZ" => array( 56, 3600, false ), |
87
|
|
|
"MSD" => array( 57, 14400, false ), |
88
|
|
|
"MSK" => array( 58, 10800, false ), |
89
|
|
|
"MST" => array( 59, -25200, false ), |
90
|
|
|
"N" => array( 60, -3600, true ), |
91
|
|
|
"NDT" => array( 61, -9000, false ), |
92
|
|
|
"NFT" => array( 62, 41400, false ), |
93
|
|
|
"NST" => array( 63, -12600, false ), |
94
|
|
|
"O" => array( 64, -7200, true ), |
95
|
|
|
"P" => array( 65, -10800, true ), |
96
|
|
|
"PDT" => array( 66, -25200, false ), |
97
|
|
|
"PST" => array( 67, -28800, false ), |
98
|
|
|
"Q" => array( 68, -14400, true ), |
99
|
|
|
"R" => array( 69, -18000, true ), |
100
|
|
|
"S" => array( 70, -21600, true ), |
101
|
|
|
"T" => array( 71, -25200, true ), |
102
|
|
|
"U" => array( 72, -28800, true ), |
103
|
|
|
"V" => array( 73, -32400, true ), |
104
|
|
|
"W" => array( 74, -36000, true ), |
105
|
|
|
"WDT" => array( 75, 32400, false ), |
106
|
|
|
"WEDT" => array( 76, 3600, false ), |
107
|
|
|
"WEST" => array( 77, 3600, false ), |
108
|
|
|
"WET" => array( 78, 0, false ), |
109
|
|
|
"WST" => array( 79, 28800, false ), |
110
|
|
|
"X" => array( 80, -39600, true ), |
111
|
|
|
"Y" => array( 81, -43200, true ), |
112
|
|
|
); |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* Generated from the DateTimeZone::listAbbreviations and contains "Area/Location", |
116
|
|
|
* e.g. "America/New_York". |
117
|
|
|
* |
118
|
|
|
* Citing https://en.wikipedia.org/wiki/Tz_database which describes that " ... |
119
|
|
|
* The underscore character is used in place of spaces. Hyphens are used |
120
|
|
|
* where they appear in the name of a location ... names have a maximum |
121
|
|
|
* length of 14 characters ..." |
122
|
|
|
* |
123
|
|
|
* @var array |
124
|
|
|
*/ |
125
|
|
|
private static $dateTimeZoneList = array(); |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @var array |
129
|
|
|
*/ |
130
|
|
|
private static $offsetCache = array(); |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @since 2.5 |
134
|
|
|
* |
135
|
|
|
* @return array |
136
|
|
|
*/ |
137
|
|
|
public static function listShortAbbreviations() { |
138
|
|
|
return array_keys( self::$shortList ); |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @since 2.5 |
143
|
|
|
* |
144
|
|
|
* @param string $identifer |
145
|
|
|
* |
146
|
|
|
* @return boolean |
147
|
|
|
*/ |
148
|
|
|
public static function isValid( $identifer ) { |
149
|
|
|
|
150
|
|
|
$identifer = str_replace( ' ', '_', $identifer ); |
151
|
|
|
|
152
|
|
|
if ( isset( self::$shortList[strtoupper( $identifer )] ) ) { |
153
|
|
|
return true; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
$dateTimeZoneList = self::getDateTimeZoneList(); |
157
|
|
|
|
158
|
|
|
if ( isset( $dateTimeZoneList[$identifer] ) ) { |
159
|
|
|
return true; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
return false; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @since 2.5 |
167
|
|
|
* |
168
|
|
|
* @param string $abbreviation |
169
|
|
|
* |
170
|
|
|
* @return boolean |
171
|
|
|
*/ |
172
|
|
|
public static function isMilitary( $abbreviation ) { |
173
|
|
|
|
174
|
|
|
$abbreviation = strtoupper( $abbreviation ); |
175
|
|
|
|
176
|
|
|
if ( isset( self::$shortList[$abbreviation] ) ) { |
177
|
|
|
return self::$shortList[$abbreviation][2]; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
return false; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* @since 2.5 |
185
|
|
|
* |
186
|
|
|
* @param string $identifer |
187
|
|
|
* |
188
|
|
|
* @return false|integer |
189
|
|
|
*/ |
190
|
|
|
public static function getIdByAbbreviation( $identifer ) { |
191
|
|
|
|
192
|
|
|
if ( isset( self::$shortList[strtoupper( $identifer )] ) ) { |
193
|
|
|
return self::$shortList[strtoupper( $identifer )][0]; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
$identifer = str_replace( ' ', '_', $identifer ); |
197
|
|
|
$dateTimeZoneList = self::getDateTimeZoneList(); |
198
|
|
|
|
199
|
|
|
if ( isset( $dateTimeZoneList[$identifer] ) ) { |
200
|
|
|
return $dateTimeZoneList[$identifer]; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
return false; |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* @since 2.5 |
208
|
|
|
* |
209
|
|
|
* @param integer $identifer |
210
|
|
|
* |
211
|
|
|
* @return false|string |
212
|
|
|
*/ |
213
|
|
|
public static function getTimezoneLiteralById( $identifer ) { |
214
|
|
|
|
215
|
|
|
foreach ( self::$shortList as $abbreviation => $value ) { |
216
|
|
|
if ( is_numeric( $identifer ) && $value[0] == $identifer ) { |
217
|
|
|
return $abbreviation; |
218
|
|
|
} |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
$dateTimeZoneList = self::getDateTimeZoneList(); |
222
|
|
|
|
223
|
|
|
if ( ( $abbreviation = array_search( $identifer, $dateTimeZoneList ) ) !== false ) { |
224
|
|
|
return $abbreviation; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
return false; |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
/** |
231
|
|
|
* @since 2.5 |
232
|
|
|
* |
233
|
|
|
* @param string $abbreviation |
234
|
|
|
* |
235
|
|
|
* @return false|string |
236
|
|
|
*/ |
237
|
|
|
public static function getOffsetByAbbreviation( $abbreviation ) { |
238
|
|
|
|
239
|
|
|
if ( isset( self::$shortList[strtoupper( $abbreviation )] ) ) { |
240
|
|
|
return self::$shortList[strtoupper( $abbreviation )][1]; |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
$abbreviation = str_replace( ' ', '_', $abbreviation ); |
244
|
|
|
|
245
|
|
|
if ( isset( self::$offsetCache[$abbreviation] ) ) { |
246
|
|
|
return self::$offsetCache[$abbreviation]; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
$offset = false; |
250
|
|
|
|
251
|
|
|
try { |
252
|
|
|
$dateTimeZone = new DateTimeZone( $abbreviation ); |
253
|
|
|
$offset = $dateTimeZone->getOffset( new DateTime() ); |
254
|
|
|
} catch( \Exception $e ) { |
255
|
|
|
// |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return self::$offsetCache[$abbreviation] = $offset; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @since 2.5 |
263
|
|
|
* |
264
|
|
|
* @param string $abbreviation |
265
|
|
|
* |
266
|
|
|
* @return string |
267
|
|
|
*/ |
268
|
|
|
public static function getNameByAbbreviation( $abbreviation ) { |
269
|
|
|
|
270
|
|
|
$abbreviation = strtoupper( $abbreviation ); |
271
|
|
|
|
272
|
|
|
if ( isset( self::$shortList[$abbreviation] ) ) { |
273
|
|
|
$name = timezone_name_from_abbr( $abbreviation ); |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
// If the abbrevation couldn't be matched use the offset instead |
277
|
|
|
if ( !$name ) { |
|
|
|
|
278
|
|
|
$name = timezone_name_from_abbr( |
279
|
|
|
"", |
280
|
|
|
self::getOffsetByAbbreviation( $abbreviation ) * 3600, |
281
|
|
|
0 |
282
|
|
|
); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
return $name; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @since 2.5 |
290
|
|
|
* |
291
|
|
|
* @param string $abbreviation |
292
|
|
|
* |
293
|
|
|
* @return DateInterval |
294
|
|
|
*/ |
295
|
|
|
public static function newDateIntervalWithOffsetBy( $abbreviation ) { |
296
|
|
|
|
297
|
|
|
$minutes = 0; |
|
|
|
|
298
|
|
|
$hour = 0; |
|
|
|
|
299
|
|
|
|
300
|
|
|
// Here we don't care for +/-, the caller of the function |
301
|
|
|
// has to care for it |
302
|
|
|
$offsetInSeconds = abs( self::getOffsetByAbbreviation( $abbreviation ) ); |
303
|
|
|
|
304
|
|
|
return new DateInterval( "PT{$offsetInSeconds}S" ); |
305
|
|
|
} |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* @since 2.5 |
309
|
|
|
* |
310
|
|
|
* @param string $abbreviation |
311
|
|
|
* |
312
|
|
|
* @return false|DateTimeZone |
313
|
|
|
*/ |
314
|
|
|
public static function newDateTimeZone( $abbreviation ) { |
315
|
|
|
|
316
|
|
|
try { |
317
|
|
|
$dateTimeZone = new DateTimeZone( $abbreviation ); |
|
|
|
|
318
|
|
|
} catch( \Exception $e ) { |
319
|
|
|
if ( ( $name = self::getNameByAbbreviation( $abbreviation ) ) !== false ) { |
320
|
|
|
return new DateTimeZone( $name ); |
321
|
|
|
} |
322
|
|
|
} |
323
|
|
|
|
324
|
|
|
return false; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Generated from the DateTimeZone::listAbbreviations |
329
|
|
|
* |
330
|
|
|
* @since 2.5 |
331
|
|
|
* |
332
|
|
|
* @return array |
333
|
|
|
*/ |
334
|
|
|
public static function getDateTimeZoneList() { |
335
|
|
|
|
336
|
|
|
if ( self::$dateTimeZoneList !== array() ) { |
337
|
|
|
return self::$dateTimeZoneList; |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
$list = DateTimeZone::listIdentifiers(); |
341
|
|
|
|
342
|
|
|
foreach ( $list as $identifier ) { |
343
|
|
|
self::$dateTimeZoneList[$identifier] = $identifier; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
return self::$dateTimeZoneList; |
347
|
|
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* @since 2.5 |
351
|
|
|
* |
352
|
|
|
* @param DateTime &$dateTime |
353
|
|
|
* @param string|integer $identifer |
354
|
|
|
* |
355
|
|
|
* @return string |
356
|
|
|
*/ |
357
|
|
|
public static function getTimezoneLiteralWithModifiedDateTime( DateTime &$dateTime, $identifer = 0 ) { |
358
|
|
|
|
359
|
|
|
if ( ( $timezoneLiteral = self::getTimezoneLiteralById( $identifer ) ) === false ) { |
360
|
|
|
return ''; |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
$dateTimeZone = null; |
364
|
|
|
|
365
|
|
|
if ( !self::isMilitary( $timezoneLiteral ) && self::getOffsetByAbbreviation( $timezoneLiteral ) != 0 ) { |
366
|
|
|
$dateTimeZone = self::newDateTimeZone( $timezoneLiteral ); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
// DI is stored in UTC time therefore find and add the offset |
370
|
|
|
if ( !$dateTimeZone instanceof DateTimeZone ) { |
371
|
|
|
$dateInterval = self::newDateIntervalWithOffsetBy( $timezoneLiteral ); |
372
|
|
|
|
373
|
|
|
if ( self::getOffsetByAbbreviation( $timezoneLiteral ) > 0 ) { |
374
|
|
|
$dateTime->add( $dateInterval ); |
375
|
|
|
} else { |
376
|
|
|
$dateTime->sub( $dateInterval ); |
377
|
|
|
} |
378
|
|
|
} else { |
379
|
|
|
$dateTime->setTimezone( $dateTimeZone ); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
return $timezoneLiteral; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
} |
386
|
|
|
|
If you define a variable conditionally, it can happen that it is not defined for all execution paths.
Let’s take a look at an example:
In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.
Available Fixes
Check for existence of the variable explicitly:
Define a default value for the variable:
Add a value for the missing path: