Completed
Push — master ( eebbc1...b931d6 )
by mw
225:23 queued 190:29
created

Timezone::getTimezoneLiteralWithModifiedDateTime()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 7
nop 2
dl 0
loc 27
rs 8.439
c 0
b 0
f 0
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 ) {
0 ignored issues
show
Bug introduced by
The variable $name does not seem to be defined for all execution paths leading up to this point.

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:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

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

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
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;
0 ignored issues
show
Unused Code introduced by
$minutes is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
298
		$hour = 0;
0 ignored issues
show
Unused Code introduced by
$hour is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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 );
0 ignored issues
show
Unused Code introduced by
$dateTimeZone is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
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