TimeZoneDataParser::parseZone()   C
last analyzed

Complexity

Conditions 8
Paths 26

Size

Total Lines 47
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 32
nc 26
nop 1
dl 0
loc 47
rs 5.7377
c 0
b 0
f 0
1
<?php
2
namespace Agavi\Date;
3
4
// +---------------------------------------------------------------------------+
5
// | This file is part of the Agavi package.                                   |
6
// | Copyright (c) 2005-2011 the Agavi Project.                                |
7
// |                                                                           |
8
// | For the full copyright and license information, please view the LICENSE   |
9
// | file that was distributed with this source code. You can also view the    |
10
// | LICENSE file online at http://www.agavi.org/LICENSE.txt                   |
11
// |   vi: set noexpandtab:                                                    |
12
// |   Local Variables:                                                        |
13
// |   indent-tabs-mode: t                                                     |
14
// |   End:                                                                    |
15
// +---------------------------------------------------------------------------+
16
use Agavi\Core\Context;
17
use Agavi\Exception\AgaviException;
18
use Agavi\Exception\UnreadableException;
19
20
/**
21
 * AgaviTimeZoneDataParser allows you to retrieve the contents of the olson
22
 * time zone database files parsed into the different definitions.
23
 *
24
 * @package    agavi
25
 * @subpackage date
26
 *
27
 * @author     Dominik del Bondio <[email protected]>
28
 * @copyright  Authors
29
 * @copyright  The Agavi Project
30
 *
31
 * @since      0.11.0
32
 *
33
 * @version    $Id$
34
 */
35
class TimeZoneDataParser
36
{
37
    /**
38
     * @var        Context An Context instance.
39
     */
40
    protected $context = null;
41
42
    /**
43
     * Retrieve the current application context.
44
     *
45
     * @return     Context An Context instance.
46
     *
47
     * @author     Dominik del Bondio <[email protected]>
48
     * @since      0.11.0
49
     */
50
    final public function getContext()
51
    {
52
        return $this->context;
53
    }
54
55
    /**
56
     * Initialize this parser.
57
     *
58
     * @param      Context $context A Context instance.
59
     *
60
     * @author     Dominik del Bondio <[email protected]>
61
     * @since      0.11.0
62
     */
63
    public function initialize(Context $context)
64
    {
65
        $this->context = $context;
66
    }
67
68
    const MIN_GEN_YEAR   =  1900;
69
    const MAX_GEN_YEAR   =  2040;
70
    const MAX_YEAR_VALUE =  2147483647;
71
    const MIN_YEAR_VALUE = -2147483647;
72
73
    /**
74
     * @var        array The preprocessed rules array.
75
     */
76
    protected $rules = array();
77
78
    /**
79
     * @see        AgaviConfigParser::parse()
80
     *
81
     * @author     Dominik del Bondio <[email protected]>
82
     * @since      0.11.0
83
     */
84
    public function parse($config)
85
    {
86 View Code Duplication
        if (!is_readable($config)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
87
            $error = 'Configuration file "' . $config . '" does not exist or is unreadable';
88
            throw new UnreadableException($error);
89
        }
90
91
        return $this->parseFile($config);
92
    }
93
94
    /**
95
     * Parses the given file
96
     *
97
     * @param      string $file The full path to the file to parse.
98
     *
99
     * @return     array An array of zones and links.
100
     *
101
     * @author     Dominik del Bondio <[email protected]>
102
     * @since      0.11.0
103
     */
104
    protected function parseFile($file)
105
    {
106
        $data = file_get_contents($file);
107
108
109
        // find version info
110
        // Try to detect it in the data file. If that fails,
111
        // try to find the version -file, and use
112
        // the current file as filename. If that fails, bail out.
113
        if (!preg_match('/^#\s*@\(#\)\s*(?P<filename>\S+)\s+(?P<version>\S+)\s*$/m', $data, $meta)) {
114
            if (file_exists(dirname($file) . '/version')) {
115
                $meta = [
116
                    'filename' => realpath($file),
117
                    'version' => file_get_contents(dirname($file) . '/version')
118
                ];
119
            } else {
120
                $meta = array(
121
                    'filename' => '(unknown)',
122
                    'version' => '(unknown)',
123
                );
124
            }
125
        }
126
127
        $zoneLines = explode("\n", $data);
128
        // filter comments
129
        $zoneLines = array_filter($zoneLines, function ($line) {
130
            return !(strlen(trim($line)) == 0 || preg_match('!^\s*#!', $line));
131
        });
132
133
        $zones = array();
134
        $rules = array();
135
        $links = array();
136
        while (list($i, $line) = each($zoneLines)) { // for($i = 0, $c = count($zoneLines); $i < $c; ++$i) {
0 ignored issues
show
Unused Code introduced by
The assignment to $line is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
137
            $line = $zoneLines[$i];
138
            if (preg_match('!^\s*Rule\s*(.*)!', $line, $match)) {
139
                $cols = $this->splitLine($match[1], 9);
140
                $rule = $this->parseRule($cols);
141
                $rules[$rule['name']][] = $rule;
142
            } elseif (preg_match('!^\s*Zone\s*(.*)!', $line, $match)) {
143
                $colLines = array();
144
                $lineCols = $this->splitLine($match[1], 5);
145
                $colLines[] = $lineCols;
146
                // the until column exists so we need to fetch the continuation line
147
                if (isset($lineCols[4]) && list($i, $line) = each($zoneLines)) {
148
                    do {
149
                        $lineCols = $this->splitLine($line, 4);
150
                        $colLines[] = $lineCols;
151
                    } while (isset($lineCols[3]) && list($i, $line) = each($zoneLines));
152
                }
153
154
                $zone = $this->parseZone($colLines);
155
                $zone['source'] = $meta['filename'];
156
                $zone['version'] = $meta['version'];
157
                $zones[] = $zone;
158
            } elseif (preg_match('!^\s*Link\s+([^\s]+)\s+([^\s]+)!', $line, $match)) {
159
                // to - from
160
                $links[$match[2]] = $match[1];
161
            } elseif (preg_match('!^\s*Leap\s*(.*)!', $line, $match)) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
162
                // leap seconds are ignored
163
            } else {
164
                throw new AgaviException('Unknown line ' . $line . ' in file ' . $file);
165
            }
166
        }
167
168
        $this->prepareRules($rules);
169
        $zones = $this->generateDatatables($zones);
170
        return array('zones' => $zones, 'links' => $links, 'meta' => $meta);
171
    }
172
173
    /**
174
     * Prepares as much info for each internal rule as possible and set them in
175
     * $this->rules.
176
     *
177
     * @param      array $rules The rules.
178
     *
179
     * @author     Dominik del Bondio <[email protected]>
180
     * @since      0.11.0
181
     */
182
    protected function prepareRules($rules)
183
    {
184
        $finalRules = array();
185
186
        foreach ($rules as $name => $ruleList) {
187
            $activeRules = array();
188
            $myRules = array();
189
190
            $cnt = count($ruleList);
191
            for ($i = 0; $i < $cnt; ++$i) {
192
                $last = ($i + 1 == $cnt);
193
                $myRule = $ruleList[$i];
194
195
                if ($myRule['startYear'] == self::MIN_YEAR_VALUE) {
196
                    $year = $myRule['endYear'];
197
                } else {
198
                    $year = $myRule['startYear'];
199
                }
200
201
                // while we have active rules and the next rule is more then 1 year
202
                // beyond we need to apply the active rules to all the missing years
203
                do {
204
                    $hasNonFinalRules = false;
205
                    // check if we have any active rules which are not final, so we need to process the final ones too
206
                    foreach ($activeRules as $activeRule) {
207
                        if ($activeRule['endYear'] != self::MAX_YEAR_VALUE) {
208
                            $hasNonFinalRules = true;
209
                            break;
210
                        }
211
                    }
212
213
                    // remove all (still) active rules which don't apply anymore
214
                    foreach ($activeRules as $activeRuleIdx => $activeRule) {
215
                        if (!is_numeric($activeRule['endYear'])) {
216
                            throw new AgaviException('endYear should be numeric but was: ' . $activeRule['endYear']);
217
                        }
218
                        if ($activeRule['endYear'] < $year) {
219
                            unset($activeRules[$activeRuleIdx]);
220
                        // protect against generating final rules, they are handled in the timezone implementation
221
                        } elseif ($year != $activeRule['startYear'] && ($hasNonFinalRules || !$last)) {
222
                            // if the year is the start year this rule has already been processed for this year
223
                            $time = $this->getOnDate($year, $activeRule['month'], $activeRule['on'], $myRule['at'], 0, 0);
224
                            $myRules[] = array('time' => $time, 'rule' => $activeRule);
225
                        }
226
                    }
227
228
                    if ($year == $myRule['startYear']) {
229
                        $time = $this->getOnDate($year, $myRule['month'], $myRule['on'], $myRule['at'], 0, 0);
230
231
                        if (($myRule['endYear'] != self::MAX_YEAR_VALUE || $year == $myRule['startYear']) || $hasNonFinalRules) {
232
                            $myRules[] = array('time' => $time, 'rule' => $myRule);
233
                        }
234
235
                        if ($myRule['startYear'] != $myRule['endYear']) {
236
                            $activeRules[] = $myRule;
237
                        }
238
                    }
239
240
                    ++$year;
241
                } while (count($activeRules) && ((!$last && $ruleList[$i + 1]['startYear'] > $year) || ($last && $year < self::MAX_GEN_YEAR)));
242
            }
243
244
            usort($myRules, array(__CLASS__, 'ruleCmp'));
245
            $finalRules[$name]['activeRules'] = $activeRules;
246
            $finalRules[$name]['rules'] = $myRules;
247
        }
248
249
        $this->rules = $finalRules;
250
    }
251
252
    /**
253
     * Comparison function for usort comparing the time of 2 rules.
254
     *
255
     * @param      array $a Parameter a
256
     * @param      array $b Parameter b
257
     *
258
     * @return     int 0 if the time equals -1 if a is smaller, 1 if b is smaller.
259
     *
260
     * @author     Dominik del Bondio <[email protected]>
261
     * @since      0.11.0
262
     */
263
    public static function ruleCmp($a, $b)
264
    {
265
        if ($a['time'] == $b['time']) {
266
            return 0;
267
        }
268
        
269
        return ($a['time'] < $b['time']) ? -1 : 1;
270
    }
271
272
    /**
273
     * Returns as rules with the given name within the given limits.
274
     *
275
     * @param      string $name The name of the ruleset.
276
     * @param      int    $from The lower time limit of the rules.
277
     * @param      string $until The upper time limit as string.
278
     * @param      int    $gmtOff The gmt offset to be used.
279
     * @param      string $format The dst format.
280
     *
281
     * @return     array  The rules which matched the criteria completely
282
     *                    processed.
283
     *
284
     * @author     Dominik del Bondio <[email protected]>
285
     * @since      0.11.0
286
     */
287
    protected function getRules($name, $from, $until, $gmtOff, $format)
288
    {
289
        if (!isset($this->rules[$name])) {
290
            throw new \InvalidArgumentException('No rule with the name ' . $name . ' exists');
291
        }
292
293
        $lastDstOff = 0;
294
295
        $rules = array();
296
        $lastUntilTime = $untilTime = null;
297
        $firstHit = true;
298
        $lastRule = null;
0 ignored issues
show
Unused Code introduced by
$lastRule 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
        $lastSkippedRule = null;
300
301
        foreach ($this->rules[$name]['rules'] as $rule) {
302
            $time = $rule['time'];
303
            $dstOff = $rule['rule']['save'];
304
            $isEndless = $rule['rule']['endYear'] == self::MAX_YEAR_VALUE;
305
306 View Code Duplication
            if ($until !== null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307
                $untilDate = $this->dateStrToArray($until);
308
                $untilTime = $this->getOnDate($untilDate['year'], $untilDate['month'], array('type' => 'date', 'date' => $untilDate['day'], 'day' => null), array('secondsInDay' => $untilDate['time']['seconds'], 'type' => $untilDate['time']['type']), $gmtOff, $dstOff);
309
            }
310
311
            switch ($rule['rule']['at']['type']) {
312
                case 'wallclock':
313
                    $time -= $lastDstOff;
314
                    $time -= $gmtOff;
315
                    break;
316
317
                case 'standard':
318
                    $time -= $gmtOff;
319
                    break;
320
            }
321
322
            $lastDstOff = $dstOff;
323
324
            if ($from !== null && $time < $from) {
325
                $lastSkippedRule = $rule;
326
                // if we need to skip the first few items until we reached the desired from
327
                continue;
328
            } elseif ($firstHit) {
329
                if ($from != $time) {
330
                    $insertRuleName = sprintf(is_array($format) ? $format[0] : $format, $lastSkippedRule !== null ? $lastSkippedRule['rule']['variablePart'] : '');
331
332
                    $rules[] = array(
333
                        'time' => $from,
334
                        'rawOffset' => $gmtOff,
335
                        'dstOffset' => 0,
336
                        'name' => $insertRuleName,
337
                        'fromEndless' => false,
338
                    );
339
                }
340
                $firstHit = false;
341
            }
342
343
            if ($until !== null && $time >= $untilTime) {
344
                break;
345
            }
346
347
            $rules[] = array(
348
                'time' => $time,
349
                'rawOffset' => $gmtOff,
350
                'dstOffset' => $dstOff,
351
                'name' => sprintf(is_array($format) ? ($dstOff == 0 ? $format[0] : $format[1]) : $format, $rule['rule']['variablePart']),
352
                'fromEndless' => $isEndless,
353
            );
354
355
            $lastUntilTime = $untilTime;
356
            $lastRule = $rule;
0 ignored issues
show
Unused Code introduced by
$lastRule 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...
357
        }
358
359
        return array('rules' => $rules, 'untilTime' => $lastUntilTime, 'activeRules' => $this->rules[$name]['activeRules']);
360
    }
361
362
    /**
363
     * Generates all the zone tables by processing their rules.
364
     *
365
     * @param      array $zones The input zones tables.
366
     *
367
     * @return     array The processed zones.
368
     *
369
     * @author     Dominik del Bondio <[email protected]>
370
     * @since      0.11.0
371
     */
372
    protected function generateDatatables($zones)
373
    {
374
        $zoneTables = array();
375
376
        foreach ($zones as $zone) {
377
            $start = true;
0 ignored issues
show
Unused Code introduced by
$start 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...
378
            $myRules = array();
379
            $finalRule = array();
380
            $activeSubRules = null;
0 ignored issues
show
Unused Code introduced by
$activeSubRules 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...
381
            $lastRuleEndTime = null;
382
            $lastDstOff = 0;
0 ignored issues
show
Unused Code introduced by
$lastDstOff 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...
383
            $zoneRuleCnt = count($zone['rules']);
384
            for ($z = 0; $z < $zoneRuleCnt; ++$z) {
385
                $lastZoneRule = ($z + 1 == $zoneRuleCnt);
386
                $zoneRule = $zone['rules'][$z];
387
388
                $activeSubRules = null;
389
390
                $gmtOff = $zoneRule['gmtOff'];
391
                $rule = $zoneRule['rule'];
392
                $dstOff = is_int($rule) ? $rule : 0;
393
                $format = $zoneRule['format'];
394
                $until = $zoneRule['until'];
395
396
                // when the rule is a rule an not the dst save
397
                if (is_string($rule)) {
398
                    $rules = $this->getRules($rule, $lastRuleEndTime, $until, $gmtOff, $format);
399
                    $untilTime = $rules['untilTime'];
400
                    $activeSubRules = $rules['activeRules'];
401
                    $rules = $rules['rules'];
402
403
                    $myRules = array_merge($myRules, $rules);
404
405
                    $lastRuleEndTime = $untilTime;
406
                } else {
407 View Code Duplication
                    if ($until) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
408
                        $untilDate = $this->dateStrToArray($until);
409
                        $untilDateTime = $this->getOnDate($untilDate['year'], $untilDate['month'], array('type' => 'date', 'date' => $untilDate['day'], 'day' => null), array('secondsInDay' => $untilDate['time']['seconds'], 'type' => $untilDate['time']['type']), $gmtOff, $dstOff);
410
                    } else {
411
                        $untilDateTime = null;
412
                    }
413
414
                    if ($lastRuleEndTime !== null) {
415
                        $myRules[] = array('time' => $lastRuleEndTime, 'rawOffset' => $gmtOff, 'dstOffset' => $dstOff, 'name' => $format);
416
                    } else {
0 ignored issues
show
Unused Code introduced by
This else statement is empty and can be removed.

This check looks for the else branches of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These else branches can be removed.

if (rand(1, 6) > 3) {
print "Check failed";
} else {
    //print "Check succeeded";
}

could be turned into

if (rand(1, 6) > 3) {
    print "Check failed";
}

This is much more concise to read.

Loading history...
417
                        // TODO: we probably don't need to add the first rule at all, check this!
418
                    }
419
420
                    $lastRuleEndTime = $untilDateTime;
421
                }
422
423
                if ($lastZoneRule) {
424
                    if (count($myRules) == 0) {
425
                        // this should actually never happen!
426
                        $lastRuleStartYear = self::MIN_YEAR_VALUE;
427
                    } else {
428
                        $cal = $this->getContext()->getTranslationManager()->createCalendar();
429
                        $lastRuleStartYear = self::MIN_YEAR_VALUE;
0 ignored issues
show
Unused Code introduced by
$lastRuleStartYear 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...
430
                        for ($i = count($myRules) - 1; $i > 0; --$i) {
431
                            if (!isset($myRules[$i]['fromEndless']) || !$myRules[$i]['fromEndless']) {
432
                                break;
433
                            }
434
                        }
435
436
                        $cal->setTime($myRules[$i]['time'] * DateDefinitions::MILLIS_PER_SECOND);
437
                        // + 1 because this specifies the first year in which the final rule will apply
438
                        $lastRuleStartYear = $cal->get(DateDefinitions::YEAR) + 1;
439
                    }
440
441
                    if ($activeSubRules !== null) {
442
                        $cnt = count($activeSubRules);
443
                        if ($cnt != 0 && $cnt != 2) {
444
                            throw new AgaviException('unexpected active rule count ' . $cnt);
445
                        }
446
                        if ($cnt == 0) {
447
                            $finalRule = array('type' => 'none', 'offset' => $gmtOff, 'startYear' => $lastRuleStartYear);
448
                        } else {
449
                            // normalize the keys
450
                            $on = 0;
451
                            $off = 1;
452
                            $sr = array_values($activeSubRules);
453
454
                            if ($sr[1]['save'] > $sr[0]['save']) {
455
                                $on = 1;
456
                                $off = 0;
457
                            }
458
459
                            $finalRule = array(
460
                                'type' => 'dynamic',
461
                                'offset' => $gmtOff,
462
                                'name' => $format,
463
                                'save' => $sr[$on]['save'],
464
                                'start' => array(
465
                                    'month' => $sr[$on]['month'],
466
                                    'date' => null,
467
                                    'day_of_week' => null,
468
                                    'time' => $sr[$on]['at']['secondsInDay'] * DateDefinitions::MILLIS_PER_SECOND,
469
                                    'type' => SimpleTimeZone::WALL_TIME,
470
                                ),
471
                                'end' => array(
472
                                    'month' => $sr[$off]['month'],
473
                                    'date' => null,
474
                                    'day_of_week' => null,
475
                                    'time' => $sr[$off]['at']['secondsInDay'] * DateDefinitions::MILLIS_PER_SECOND,
476
                                    'type' => SimpleTimeZone::WALL_TIME,
477
                                ),
478
                                'startYear' => $lastRuleStartYear,
479
                            );
480
481
                            for ($i = 0; $i < count($sr); ++$i) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
482
                                if ($i == $on) {
483
                                    $frIdx = 'start';
484
                                } else {
485
                                    $frIdx = 'end';
486
                                }
487
488
                                if ($sr[$i]['at']['type'] == 'standard') {
489
                                    $finalRule[$frIdx]['type'] = SimpleTimeZone::STANDARD_TIME;
490
                                } elseif ($sr[$on]['at']['type'] == 'universal') {
491
                                    $finalRule[$frIdx]['type'] = SimpleTimeZone::UTC_TIME;
492
                                }
493
494
                                if ($sr[$i]['on']['type'] == 'date') {
495
                                    $finalRule[$frIdx]['date'] = $sr[$i]['on']['date'];
496
                                    $finalRule[$frIdx]['day_of_week'] = 0;
497
                                } elseif ($sr[$i]['on']['type'] == 'last') {
498
                                    $finalRule[$frIdx]['date'] = -1;
499
                                    $finalRule[$frIdx]['day_of_week'] = $sr[$i]['on']['day'];
500
                                } elseif ($sr[$i]['on']['type'] == '<=') {
501
                                    $finalRule[$frIdx]['date'] = -$sr[$i]['on']['date'];
502
                                    $finalRule[$frIdx]['day_of_week'] = -$sr[$i]['on']['day'];
503
                                } elseif ($sr[$i]['on']['type'] == '>=') {
504
                                    $finalRule[$frIdx]['date'] = $sr[$i]['on']['date'];
505
                                    $finalRule[$frIdx]['day_of_week'] = -$sr[$i]['on']['day'];
506
                                }
507
                            }
508
                        }
509
                    } else {
510
                        $finalRule = array('type' => 'static', 'name' => $format, 'offset' => $gmtOff, 'startYear' => $lastRuleStartYear);
511
                    }
512
                }
513
            }
514
515
            $myTypes = array();
516
517
            // compact the same (raw|dst)offset & name fields
518
            foreach ($myRules as $id => $rule) {
519
                if (is_array($rule['name'])) {
520
                    continue;
521
                }
522
                $key = sprintf('raw=%d&dst=%d&name=%s', $rule['rawOffset'], $rule['dstOffset'], $rule['name']);
523
                $myTypes[$key][] = $id;
524
            }
525
526
            $typeId = 0;
527
            $myFinalTypes = array();
528
            $myFinalRules = array();
529
            foreach ($myTypes as $key => $ids) {
530
                $firstRule = $myRules[$ids[0]];
531
                $myFinalTypes[$typeId] = array('rawOffset' => $firstRule['rawOffset'], 'dstOffset' => $firstRule['dstOffset'], 'name' => $firstRule['name']);
532
                foreach ($ids as $id) {
533
                    $myFinalRules[] = array('time' => $myRules[$id]['time'], 'type' => $typeId);
534
                }
535
                ++$typeId;
536
            }
537
538
            usort($myFinalRules, array(__CLASS__, 'ruleCmp'));
539
540
            $zoneTables[$zone['name']] = array('types' => $myFinalTypes, 'rules' => $myFinalRules, 'finalRule' => $finalRule, 'source' => $zone['source'], 'version' => $zone['version']);
541
        }
542
543
        return $zoneTables;
544
    }
545
546
    /**
547
     * Returns the time specified by the input arguments.
548
     *
549
     * @param      int $year The year.
550
     * @param      int $month The month.
551
     * @param      array $dateDef The date definition.
552
     * @param      array $atDef The at (time into the day) definition.
553
     * @param      int $gmtOff The gmt offset.
554
     * @param      int $dstOff The dst offset.
555
     *
556
     * @return     int The unix timestamp.
557
     *
558
     * @author     Dominik del Bondio <[email protected]>
559
     * @since      0.11.0
560
     */
561
    protected function getOnDate($year, $month, $dateDef, $atDef, $gmtOff, $dstOff)
562
    {
563
        static $cal = null;
564
        if (!$cal) {
565
            $cal = $this->getContext()->getTranslationManager()->createCalendar();
566
        }
567
568
        $cal->clear();
569
        $cal->set(DateDefinitions::YEAR, $year);
570
        $cal->set(DateDefinitions::MONTH, $month);
571
        if ($dateDef['type'] == 'date') {
572
            $cal->set(DateDefinitions::DATE, $dateDef['date']);
573
        } elseif ($dateDef['type'] == 'last') {
574
            $daysInMonth = CalendarGrego::monthLength($year, $month);
575
            $cal->set(DateDefinitions::DATE, $daysInMonth);
576
            // loop backwards until we found the last occurrence of the day
577
            while ($cal->get(DateDefinitions::DAY_OF_WEEK) != $dateDef['day']) {
578
                $cal->roll(DateDefinitions::DATE, -1);
579
            }
580
        } elseif ($dateDef['type'] == '<=') {
581
            $cal->set(DateDefinitions::DATE, $dateDef['date']);
582
            while ($cal->get(DateDefinitions::DAY_OF_WEEK) != $dateDef['day']) {
583
                $cal->roll(DateDefinitions::DATE, -1);
584
            }
585
        } elseif ($dateDef['type'] == '>=') {
586
            $cal->set(DateDefinitions::DATE, $dateDef['date']);
587
            while ($cal->get(DateDefinitions::DAY_OF_WEEK) != $dateDef['day']) {
588
                $cal->roll(DateDefinitions::DATE, 1);
589
            }
590
        } else {
591
            throw new AgaviException('Unknown on type ' . $dateDef['type']);
592
        }
593
        $time = $cal->getTime() / 1000;
594
595
        $time += $atDef['secondsInDay'];
596
        if ($atDef['type'] == 'wallclock') {
597
            $time -= $dstOff;
598
            $time -= $gmtOff;
599
        } elseif ($atDef['type'] == 'standard') {
600
            $time -= $gmtOff;
601
        }
602
603
        return $time;
604
    }
605
606
    /**
607
     * Splits a line into the amount of items requested according to the
608
     * olson definition (which allows the last item to contain spaces)
609
     *
610
     * @param      string $line The line.
611
     * @param      int $itemCount The amount of items.
612
     *
613
     * @return     array The items.
614
     *
615
     * @author     Dominik del Bondio <[email protected]>
616
     * @since      0.11.0
617
     */
618
    protected function splitLine($line, $itemCount)
619
    {
620
        $line = trim($line);
621
622
        $inQuote = false;
623
        $itemStr = '';
624
        $lastChar = false;
625
        $items = array();
626
        $itemPos = 0;
627
        for ($i = 0, $l = strlen($line); $i < $l; ++$i) {
628
            if ($i + 1 == $l) {
629
                $lastChar = true;
630
                $cNext = null;
631
            } else {
632
                $cNext = $line[$i+1];
633
            }
634
            $c = $line[$i];
635
636
            if (!$inQuote) {
637
                if ($c == '"') {
638
                    $inQuote = true;
639
                } elseif ($c == '#') {
640
                    // make char to space to trigger processing
641
                    $c = ' ';
642
                    $i = $l;
643
                } elseif (!ctype_space($c) || (($itemPos + 1 == $itemCount) && strlen($itemStr) > 0)) {
644
                    $itemStr .= $c;
645
                }
646
            } else {
647
                if ($c == '"' && $cNext == '"') {
648
                    $itemStr .= $c;
649
                    ++$i;
650
                } elseif ($c == '"') {
651
                    $inQuote = false;
652
                } else {
653
                    $itemStr .= $c;
654
                }
655
            }
656
657
            if (($lastChar || ctype_space($c)) && strlen($itemStr) > 0) {
658
                if (isset($items[$itemPos])) {
659
                    $itemStr = $items[$itemPos] . $itemStr;
660
                }
661
                $items[$itemPos] = $itemStr;
662
                if ($itemPos + 1 < $itemCount) {
663
                    ++$itemPos;
664
                }
665
                $itemStr = '';
666
            }
667
        }
668
669
        return array_map('trim', $items);
670
    }
671
672
    /**
673
     *            NAME  FROM  TO    TYPE  IN   ON       AT    SAVE  LETTER/S
674
     *
675
     * For example:
676
     *
677
     *      Rule  US    1967  1973  -     Apr  lastSun  2:00  1:00  D
678
     *
679
     * The fields that make up a rule line are:
680
     *
681
     *  NAME    Gives the (arbitrary) name of the set of rules this
682
     *          rule is part of.
683
     *
684
     *  FROM    Gives the first year in which the rule applies.  Any
685
     *          integer year can be supplied; the Gregorian calendar
686
     *          is assumed.  The word minimum (or an abbreviation)
687
     *          means the minimum year representable as an integer.
688
     *          The word maximum (or an abbreviation) means the
689
     *          maximum year representable as an integer.  Rules can
690
     *          describe times that are not representable as time
691
     *          values, with the unrepresentable times ignored; this
692
     *          allows rules to be portable among hosts with
693
     *          differing time value types.
694
     *
695
     *  TO      Gives the final year in which the rule applies.  In
696
     *          addition to minimum and maximum (as above), the word
697
     *          only (or an abbreviation) may be used to repeat the
698
     *          value of the FROM field.
699
     *
700
     *  TYPE    Gives the type of year in which the rule applies.
701
     *          If TYPE is - then the rule applies in all years
702
     *          between FROM and TO inclusive.  If TYPE is something
703
     *          else, then zic executes the command
704
     *               yearistype year type
705
     *          to check the type of a year:  an exit status of zero
706
     *          is taken to mean that the year is of the given type;
707
     *          an exit status of one is taken to mean that the year
708
     *          is not of the given type.
709
     *
710
     *  IN      Names the month in which the rule takes effect.
711
     *          Month names may be abbreviated.
712
     *
713
     *  ON      Gives the day on which the rule takes effect.
714
     *          Recognized forms include:
715
     *
716
     *               5        the fifth of the month
717
     *               lastSun  the last Sunday in the month
718
     *               lastMon  the last Monday in the month
719
     *               Sun>=8   first Sunday on or after the eighth
720
     *               Sun<=25  last Sunday on or before the 25th
721
     *
722
     *          Names of days of the week may be abbreviated or
723
     *          spelled out in full.  Note that there must be no
724
     *          spaces within the ON field.
725
     *
726
     *  AT      Gives the time of day at which the rule takes
727
     *          effect.  Recognized forms include:
728
     *
729
     *               2        time in hours
730
     *               2:00     time in hours and minutes
731
     *               15:00    24-hour format time (for times after noon)
732
     *               1:28:14  time in hours, minutes, and seconds
733
     *               -        equivalent to 0
734
     *
735
     *          where hour 0 is midnight at the start of the day,
736
     *          and hour 24 is midnight at the end of the day.  Any
737
     *          of these forms may be followed by the letter w if
738
     *          the given time is local "wall clock" time, s if the
739
     *          given time is local "standard" time, or u (or g or
740
     *          z) if the given time is universal time; in the
741
     *          absence of an indicator, wall clock time is assumed.
742
     *
743
     *  SAVE    Gives the amount of time to be added to local
744
     *          standard time when the rule is in effect.  This
745
     *          field has the same format as the AT field (although,
746
     *          of course, the w and s suffixes are not used).
747
     *
748
     *  LETTER/S
749
     *          Gives the "variable part" (for example, the "S" or
750
     *          "D" in "EST" or "EDT") of time zone abbreviations to
751
     *          be used when this rule is in effect.  If this field
752
     *          is -, the variable part is null.
753
     */
754
    /**
755
     * Parses a rule.
756
     *
757
     * @param      array $ruleColumns The columns of this rule.
758
     *
759
     * @return     array The parsed rule.
760
     *
761
     * @author     Dominik del Bondio <[email protected]>
762
     * @since      0.11.0
763
     */
764
    protected function parseRule($ruleColumns)
765
    {
766
767
        $name = $ruleColumns[0];
768
        $startYear = $ruleColumns[1];
769
        if (substr_compare($startYear, 'mi', 0, 2, true) == 0) {
770
            $startYear = self::MIN_YEAR_VALUE;
771
        } elseif (substr_compare($startYear, 'ma', 0, 2, true) == 0) {
772
            $startYear = self::MAX_YEAR_VALUE;
773
        }
774
        $endYear = $ruleColumns[2];
775
        if (substr_compare($endYear, 'mi', 0, 2, true) == 0) {
776
            $endYear = self::MIN_YEAR_VALUE;
777
        } elseif (substr_compare($endYear, 'ma', 0, 2, true) == 0) {
778
            $endYear = self::MAX_YEAR_VALUE;
779
        } elseif (substr_compare($endYear, 'o', 0, 1, true) == 0) {
780
            $endYear = $startYear;
781
        }
782
783
        $type = $ruleColumns[3];
784
        if ($type != '-') {
785
            throw new AgaviException('Unknown type "' . $type . '" in rule ' . $name);
786
        }
787
788
        $month = $this->getMonthFromAbbr($ruleColumns[4]);
789
        if (!is_numeric($month)) {
790
            throw new AgaviException('Unknown month "'.$month.'" in rule ' . $name);
791
        }
792
793
        $on = $ruleColumns[5];
794
        if (is_numeric($on)) {
795
            $on = array('type' => 'date', 'date' => $on, 'day' => null);
796
        } elseif (preg_match('!^last(.*)$!', $on, $match)) {
797
            $day = $this->getDayFromAbbr($match[1]);
798
            if (!is_numeric($day)) {
799
                throw new AgaviException('Unknown day "'.$day.'" in rule ' . $name);
800
            }
801
802
            $on = array('type' => 'last', 'date' => null, 'day' => $day);
803
        } elseif (preg_match('!^([a-z]+)(\>\=|\<\=)([0-9]+)$!i', $on, $match)) {
804
            $day = $this->getDayFromAbbr($match[1]);
805
            if (!is_numeric($day)) {
806
                throw new AgaviException('Unknown day "'.$day.'" in rule ' . $name);
807
            }
808
809
            $on = array('type' => $match[2], 'date' => $match[3], 'day' => $day);
810
        } else {
811
            throw new AgaviException('unknown on column (' . $on . ') in rule ' . $name);
812
        }
813
814
        $at = $ruleColumns[6];
815
        $lastAtChar = substr($at, -1);
816
        $atType = 'wallclock';
817 View Code Duplication
        if ($lastAtChar == 'w') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
818
            $at = substr($at, 0, -1);
819
        } elseif ($lastAtChar == 's') {
820
            $atType = 'standard';
821
            $at = substr($at, 0, -1);
822
        } elseif ($lastAtChar == 'u' || $lastAtChar == 'z' || $lastAtChar == 'g') {
823
            $atType = 'universal';
824
            $at = substr($at, 0, -1);
825
        }
826
827
        if ($at == '-') {
828
            $at = 0;
829
        } else {
830
            $at = $this->timeStrToSeconds($at);
831
        }
832
833
        $at = array('type' => $atType, 'secondsInDay' => $at);
834
835
        $save = $this->timeStrToSeconds($ruleColumns[7]);
836
837
        $variablePart = $ruleColumns[8];
838
        if ($variablePart == '-') {
839
            $variablePart = '';
840
        }
841
842
        return array(
843
            'name' => $name,
844
            'startYear' => $startYear,
845
            'endYear' => $endYear,
846
            'type' => $type,
847
            'month' => $month,
848
            'on' => $on,
849
            'at' => $at,
850
            'save' => $save,
851
            'variablePart' => $variablePart
852
        );
853
    }
854
855
    /*
856
	 *       NAME                GMTOFF  RULES/SAVE  FORMAT  [UNTIL]
857
	 *
858
	 * For example:
859
	 *
860
	 *       Australia/Adelaide  9:30    Aus         CST     1971 Oct 31 2:00
861
	 *
862
	 * The fields that make up a zone line are:
863
	 *
864
	 *  NAME  The name of the time zone.  This is the name used in
865
	 *        creating the time conversion information file for the
866
	 *        zone.
867
	 *
868
	 *  GMTOFF
869
	 *        The amount of time to add to UTC to get standard time
870
	 *        in this zone.  This field has the same format as the
871
	 *        AT and SAVE fields of rule lines; begin the field with
872
	 *        a minus sign if time must be subtracted from UTC.
873
	 *
874
	 *  RULES/SAVE
875
	 *        The name of the rule(s) that apply in the time zone
876
	 *        or, alternately, an amount of time to add to local
877
	 *        standard time.  If this field is - then standard time
878
	 *        always applies in the time zone.
879
	 *
880
	 *  FORMAT
881
	 *        The format for time zone abbreviations in this time
882
	 *        zone.  The pair of characters %s is used to show where
883
	 *        the "variable part" of the time zone abbreviation
884
	 *        goes.  Alternately, a slash (/) separates standard and
885
	 *        daylight abbreviations.
886
	 *
887
	 *  UNTIL The time at which the UTC offset or the rule(s) change
888
	 *        for a location.  It is specified as a year, a month, a
889
	 *        day, and a time of day.  If this is specified, the
890
	 *        time zone information is generated from the given UTC
891
	 *        offset and rule change until the time specified.  The
892
	 *        month, day, and time of day have the same format as
893
	 *        the IN, ON, and AT columns of a rule; trailing columns
894
	 *        can be omitted, and default to the earliest possible
895
	 *        value for the missing columns.
896
	 *
897
	 *        The next line must be a "continuation" line; this has
898
	 *        the same form as a zone line except that the string
899
	 *        "Zone" and the name are omitted, as the continuation
900
	 *        line will place information starting at the time
901
	 *        specified as the UNTIL field in the previous line in
902
	 *        the file used by the previous line.  Continuation
903
	 *        lines may contain an UNTIL field, just as zone lines
904
	 *        do, indicating that the next line is a further
905
	 *        continuation.
906
	 */
907
    /**
908
     * Parses a zone.
909
     *
910
     * @param      array $zoneLines The lines of this zone.
911
     *
912
     * @return     array The parsed zone.
913
     *
914
     * @author     Dominik del Bondio <[email protected]>
915
     * @since      0.11.0
916
     */
917
    protected function parseZone($zoneLines)
918
    {
919
        $indexBase = 0;
920
        $i = 0;
921
        $c = count($zoneLines);
922
923
        $name = $zoneLines[$i][0];
924
925
        $rules = array();
926
927
        do {
928
            $zoneColumns = $zoneLines[$i];
929
            $gmtOff = $zoneColumns[$indexBase + 1];
930
            if ($gmtOff[0] == '-') {
931
                $gmtOff = - $this->timeStrToSeconds(substr($gmtOff, 1));
932
            } else {
933
                $gmtOff = $this->timeStrToSeconds($gmtOff);
934
            }
935
936
            $rule = $zoneColumns[$indexBase + 2];
937
            if ($rule == '-') {
938
                $rule = null;
939
            } elseif (preg_match('!^[^\s0-9][^\s]+$!', $rule)) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
940
            } elseif (preg_match('!^([0-9]+):([0-9]+)!', $rule, $match)) {
941
                $rule = $match[1] * 3600 + $match[2] * 60;
942
            } else {
943
                throw new AgaviException('Unknown rule column "' . $rule . '" in zone ' . $name);
944
            }
945
946
            $format = $zoneColumns[$indexBase + 3];
947
            if (strpos($format, '/') !== false) {
948
                $format = explode('/', $format);
949
            }
950
951
            $until = null;
952
            if (isset($zoneColumns[$indexBase + 4])) {
953
                $until = $zoneColumns[$indexBase + 4];
954
            }
955
956
            $rules[] = array('gmtOff' => $gmtOff, 'rule' => $rule, 'format' => $format, 'until' => $until);
957
958
            $indexBase = -1;
959
            ++$i;
960
        } while ($i < $c);
961
962
        return array('name' => $name, 'rules' => $rules);
963
    }
964
965
    /**
966
     * Determines the month definition from an abbreviation.
967
     *
968
     * @param      string $month The abbreviated month.
969
     *
970
     * @return     int The definition of this month from AgaviDateDefinitions.
971
     *
972
     * @author     Dominik del Bondio <[email protected]>
973
     * @since      0.11.0
974
     */
975
    protected function getMonthFromAbbr($month)
976
    {
977
        static $months = array(DateDefinitions::JANUARY => 'january', DateDefinitions::FEBRUARY => 'february', DateDefinitions::MARCH => 'march', DateDefinitions::APRIL => 'april', DateDefinitions::MAY => 'may', DateDefinitions::JUNE => 'june', DateDefinitions::JULY => 'july', DateDefinitions::AUGUST => 'august', DateDefinitions::SEPTEMBER => 'september', DateDefinitions::OCTOBER => 'october', DateDefinitions::NOVEMBER => 'november', DateDefinitions::DECEMBER => 'december');
978
979 View Code Duplication
        foreach ($months as $i => $m) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
980
            if (substr_compare($m, $month, 0, strlen($month), true) == 0) {
981
                $month = $i;
982
                break;
983
            }
984
        }
985
986
        return $month;
987
    }
988
989
    /**
990
     * Determines the day definition from an abbreviation.
991
     *
992
     * @param      string $day The abbreviated day.
993
     *
994
     * @return     int The definition of this day from AgaviDateDefinitions.
995
     *
996
     * @author     Dominik del Bondio <[email protected]>
997
     * @since      0.11.0
998
     */
999
    protected function getDayFromAbbr($day)
1000
    {
1001
        static $days = array(DateDefinitions::SUNDAY => 'sunday', DateDefinitions::MONDAY => 'monday', DateDefinitions::TUESDAY => 'tuesday', DateDefinitions::WEDNESDAY => 'wednesday', DateDefinitions::THURSDAY => 'thursday', DateDefinitions::FRIDAY => 'friday', DateDefinitions::SATURDAY => 'saturday');
1002
1003 View Code Duplication
        foreach ($days as $i => $d) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1004
            if (substr_compare($d, $day, 0, strlen($day), true) == 0) {
1005
                $day = $i;
1006
                break;
1007
            }
1008
        }
1009
1010
        return $day;
1011
    }
1012
1013
    /**
1014
     * Returns the seconds from a string in the hh:mm:ss format.
1015
     *
1016
     * @param      string $time The time as string.
1017
     *
1018
     * @return     int The seconds into the day defined by the input.
1019
     *
1020
     * @author     Dominik del Bondio <[email protected]>
1021
     * @since      0.11.0
1022
     */
1023
    protected function timeStrToSeconds($time)
1024
    {
1025
        if (preg_match('!^(-?)([0-9]{1,2})(\:[0-9]{1,2})?(\:[0-9]{1,2})?$!', $time, $match)) {
1026
            $seconds = 0;
1027
            if (isset($match[4])) {
1028
                $seconds += substr($match[4], 1);
1029
            }
1030
            if (isset($match[3])) {
1031
                $seconds += substr($match[3], 1) * 60;
1032
            }
1033
            $seconds += $match[2] * 60 * 60;
1034
            if ($match[1] == '-') {
1035
                $seconds = -$seconds;
1036
            }
1037
        } elseif ($time == '-') {
1038
            $seconds = 0;
1039
        } else {
1040
            throw new AgaviException('unknown time format "' . $time . '"');
1041
        }
1042
1043
        return $seconds;
1044
    }
1045
1046
    /**
1047
     * Parses a date string and returns its parts as array.
1048
     *
1049
     * @param      string $date The date as string.
1050
     *
1051
     * @return     array The parts of the date.
1052
     *
1053
     * @author     Dominik del Bondio <[email protected]>
1054
     * @since      0.11.0
1055
     */
1056
    protected function dateStrToArray($date)
1057
    {
1058
        $array = array('year' => 0, 'month' => 0, 'day' => 1, 'time' => array('type' => 'wallclock', 'seconds' => 0));
1059
        if (preg_match('!(\d{4})(\s+[a-z0-9]+)?(\s+\d+)?(\s+\d[^\s]*)?!i', $date, $match)) {
1060
            $match = array_map('trim', $match);
1061
            $array['year'] = $match[1];
1062
            if (isset($match[2])) {
1063
                $array['month'] = $this->getMonthFromAbbr($match[2]);
1064
            }
1065
            if (isset($match[3])) {
1066
                $array['day'] = $match[3];
1067
            }
1068
            if (isset($match[4])) {
1069
                $type = 'wallclock';
1070
                $time = $match[4];
1071
                $lastChar = substr($time, -1);
1072 View Code Duplication
                if ($lastChar == 'w') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1073
                    $time = substr($time, 0, -1);
1074
                } elseif ($lastChar == 's') {
1075
                    $type = 'standard';
1076
                    $time = substr($time, 0, -1);
1077
                } elseif ($lastChar == 'u' || $lastChar == 'z' || $lastChar == 'g') {
1078
                    $type = 'universal';
1079
                    $time = substr($time, 0, -1);
1080
                }
1081
                $array['time'] = array('type' => $type, 'seconds' => $this->timeStrToSeconds($time));
1082
            }
1083
        } else {
1084
            throw new AgaviException('unknown date format: "' . $date . '"');
1085
        }
1086
1087
        return $array;
1088
    }
1089
}
1090