DateFormat::matchStringWithFallbacks()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 3
nop 4
dl 0
loc 16
rs 9.2
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\Translation\Locale;
19
20
/**
21
 * This class lets your format date and time according to a given format
22
 * definition.
23
 *
24
 * @package    agavi
25
 * @subpackage util
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 DateFormat
36
{
37
    /**
38
     * @var        string The format string given by the user
39
     */
40
    protected $originalFormatString = null;
41
42
    /**
43
     * @var        string The format string which will be given to sprintf
44
     */
45
    protected $formatString = '';
46
47
    /**
48
     * @var        Context An Context instance.
49
     */
50
    protected $context = null;
51
52
    /**
53
     * Constructs a new date formatter.
54
     *
55
     * @param      string $format Format to be used for formatting.
56
     *
57
     * @author     Dominik del Bondio <[email protected]>
58
     * @since      0.11.0
59
     */
60
    public function __construct($format = null)
61
    {
62
        if ($format) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $format of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
63
            $this->setFormat($format);
64
        }
65
    }
66
    
67
    /**
68
     * Initialize this Format.
69
     *
70
     * @param      Context $context The current application context.
71
     * @param      array   $parameters An associative array of initialization parameters
72
     *
73
     * @author     Dominik del Bondio <[email protected]>
74
     * @since      1.0.0
75
     */
76
    public function initialize(Context $context, array $parameters = array())
77
    {
78
        $this->context = $context;
79
    }
80
    
81
    /**
82
     * Retrieve the current application context.
83
     *
84
     * @return     Context The current Context instance.
85
     *
86
     * @author     Dominik del Bondio <[email protected]>
87
     * @since      1.0.0
88
     */
89
    final public function getContext()
90
    {
91
        return $this->context;
92
    }
93
    
94
95
    const T_TEXT                  = -1;
96
97
    const T_ERA                   = 0;
98
    const T_YEAR                  = 1;
99
    const T_MONTH                 = 2;
100
    const T_DATE                  = 3;
101
    const T_HOUR_1_24             = 4;
102
    const T_HOUR_0_23             = 5;
103
    const T_MINUTE                = 6;
104
    const T_SECOND                = 7;
105
    const T_FRACTIONAL_SECOND     = 8;
106
    const T_DAY_OF_WEEK           = 9;
107
    const T_DAY_OF_YEAR           = 10;
108
    const T_DAY_OF_WEEK_IN_MONTH  = 11;
109
    const T_WEEK_OF_YEAR          = 12;
110
    const T_WEEK_OF_MONTH         = 13;
111
    const T_AM_PM                 = 14;
112
    const T_HOUR_1_12             = 15;
113
    const T_HOUR_0_11             = 16;
114
    const T_TIMEZONE              = 17;
115
    const T_ISO_YEAR              = 18;
116
    const T_LOCAL_DAY_OF_WEEK     = 19;
117
    const T_EXTENDED_YEAR         = 20;
118
    const T_MODIFIED_JD           = 21;
119
    const T_MS_IN_DAY             = 22;
120
    const T_TIMEZONE_RFC          = 23;
121
    const T_TIMEZONE_GENERIC      = 24;
122
    const T_SA_LOCAL_DAY_OF_WEEK  = 25;
123
    const T_SA_MONTH              = 26;
124
    const T_QUARTER               = 27;
125
    const T_SA_QUARTER            = 28;
126
127
    /**
128
     * @var        array The default mapping of format characters to their
129
     *                   meanings.
130
     */
131
    protected static $defaultMap = array(
132
        'G' => self::T_ERA,
133
        'y' => self::T_YEAR,
134
        'M' => self::T_MONTH,
135
        'd' => self::T_DATE,
136
        'k' => self::T_HOUR_1_24,
137
        'H' => self::T_HOUR_0_23,
138
        'm' => self::T_MINUTE,
139
        's' => self::T_SECOND,
140
        'S' => self::T_FRACTIONAL_SECOND,
141
        'E' => self::T_DAY_OF_WEEK,
142
        'D' => self::T_DAY_OF_YEAR,
143
        'F' => self::T_DAY_OF_WEEK_IN_MONTH,
144
        'w' => self::T_WEEK_OF_YEAR,
145
        'W' => self::T_WEEK_OF_MONTH,
146
        'a' => self::T_AM_PM,
147
        'h' => self::T_HOUR_1_12,
148
        'K' => self::T_HOUR_0_11,
149
        'z' => self::T_TIMEZONE,
150
        'Y' => self::T_ISO_YEAR,
151
        'e' => self::T_LOCAL_DAY_OF_WEEK,
152
        'u' => self::T_EXTENDED_YEAR,
153
        'g' => self::T_MODIFIED_JD,
154
        'A' => self::T_MS_IN_DAY,
155
        'Z' => self::T_TIMEZONE_RFC,
156
        'v' => self::T_TIMEZONE_GENERIC,
157
        'c' => self::T_SA_LOCAL_DAY_OF_WEEK,
158
        'L' => self::T_SA_MONTH,
159
        'Q' => self::T_QUARTER,
160
        'q' => self::T_SA_QUARTER,
161
    );
162
163
    /**
164
     * @var        array The list of tokens in the format.
165
     */
166
    protected $tokenList;
167
168
    /**
169
     * Returns the format which is currently used to format dates.
170
     *
171
     * @return     string The current format.
172
     *
173
     * @author     Dominik del Bondio <[email protected]>
174
     * @since      0.11.0
175
     */
176
    public function getFormat()
177
    {
178
        return $this->originalFormatString;
179
    }
180
181
    /**
182
     * Sets the format which should be used.
183
     *
184
     * @param      string $format Format to be used for formatting.
185
     *
186
     * @author     Dominik del Bondio <[email protected]>
187
     * @since      0.11.0
188
     */
189
    public function setFormat($format)
190
    {
191
        $this->internalParseFormat($format, self::$defaultMap);
192
    }
193
194
    /**
195
     * Sets the format which should be used. This will use the the format
196
     * characters specified in the locale instead the default ones.
197
     *
198
     * NOTE: this function is not implemented yet!
199
     *
200
     * @param      string $format Format to be used for formatting.
201
     * @param      Locale $locale The locale.
202
     *
203
     * @author     Dominik del Bondio <[email protected]>
204
     * @since      0.11.0
205
     */
206
    public function setLocalizedFormat($format, Locale $locale)
0 ignored issues
show
Unused Code introduced by
The parameter $format is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $locale is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
207
    {
208
    }
209
210
    /**
211
     * Formats a given date.
212
     *
213
     * @param      mixed $data           The date. This can either be an array containing all the
214
     *                                   needed info with the AgaviDateDefinitions constants as
215
     *                                   keys or an unix timestamp (doesn't work yet!) or a
216
     *                                   Calendar instance.
217
     * @param      string $calendarType  The calendar type this date should be formatted in
218
     *                                   (this will usually be gregorian)
219
     * @param      Locale|string $locale The locale to format the date in.
220
     *
221
     * @return     string The formatted date.
222
     *
223
     * @author     Dominik del Bondio <[email protected]>
224
     * @since      0.11.0
225
     */
226
    public function format($data, $calendarType = Calendar::GREGORIAN, $locale = null)
227
    {
228 View Code Duplication
        if ($locale instanceof Locale) {
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...
229
            $tm = $locale->getContext()->getTranslationManager();
230
        } elseif ($this->context) {
231
            $tm = $this->context->getTranslationManager();
232
            if ($locale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $locale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
233
                $locale = $tm->getLocale($locale);
234
            } else {
235
                $locale = $tm->getCurrentLocale();
236
            }
237
        } else {
238
            throw new \InvalidArgumentException('This AgaviDateFormat has not been initialize()d. To be able to pass a string as locale you need to call initialize() or create this DateFormat using TranslationManager::createDateFormat()');
239
        }
240
        
241
        $tzid = null;
0 ignored issues
show
Unused Code introduced by
$tzid 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...
242
        if ($locale->getLocaleTimeZone()) {
243
            // use the timezone from the current/provided locale if available
244
            $tzid = $tm->resolveTimeZoneId($locale->getLocaleTimeZone());
245
        } else {
246
            // otherwise use the default timezone
247
            $tzid = $tm->getDefaultTimeZone()->getResolvedId();
248
        }
249
        
250
        $cal = null;
251
        // remember if cloning the calendar is required on changes
252
        // (this is only true if the calendar was passed to this method and not
253
        // implicitly created)
254
        $calNeedsClone = false;
255
        if ($data instanceof Calendar) {
256
            $cal = $data;
257
            $calNeedsClone = true;
258
        } elseif ($data instanceof \DateTime) {
259
            $cal = $this->context->getTranslationManager()->createCalendar($data);
260
        } elseif (is_int($data)) {
261
            $cal = $this->context->getTranslationManager()->createCalendar($locale);
262
            $cal->setUnixTimestamp($data);
263
        } elseif (is_array($data)) {
264
            $cal = $tm->createCalendar();
265
            $cal->fromArray($data);
266
        } else {
267
            // $data is most likely a string a this point (or something which can be
268
            // implicitly converted to a string, so there is no explicit is_string check)
269
            try {
270
                // maybe it is a date/time string we can parse...
271
                $cal = $tm->createCalendar(new \DateTime($data));
272
            } catch (\Exception $e) {
273
                // err... no, it isn't. try to use the message as a calendar name
274
                $cal = $tm->createCalendar($data);
275
            }
276
        }
277
        
278
        if ($tzid != $cal->getTimeZone()->getResolvedId()) {
279
            // the requested timezone for use in the output differs from the timezone
280
            // set in the calendar: make the calendar reside in the new timezone
281
            // so it calculates the correct date values for that timezone
282
            
283
            if ($calNeedsClone) {
284
                $cal = clone $cal;
285
            }
286
            $cal->setTimeZone($tm->createTimeZone($tzid));
287
        }
288
        
289
        $data = $cal->toArray();
290
        
291
        $out = '';
292
293
        foreach ($this->tokenList as $token) {
294
            $count = $token[1];
295
            switch ($token[0]) {
296
                case self::T_TEXT:
297
                    $out .= $token[1];
298
                    break;
299
300 View Code Duplication
                case self::T_ERA:
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...
301
                    $era = $data[DateDefinitions::ERA];
302
                    if ($count == 4) {
303
                        $out .= $locale->getCalendarEraWide($calendarType, $era);
304
                    } elseif ($count == 5) {
305
                        $out .= $locale->getCalendarEraNarrow($calendarType, $era);
306
                    } else {
307
                        $out .= $locale->getCalendarEraAbbreviated($calendarType, $era);
308
                    }
309
                    break;
310
311 View Code Duplication
                case self::T_YEAR:
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...
312
                    $year = $data[DateDefinitions::YEAR];
313
                    if ($count == 2) {
314
                        // strip year to 2 chars
315
                        $year = $year % 100;
316
                    }
317
                    $out .= str_pad($year, $count, '0', STR_PAD_LEFT);
318
                    break;
319
320
                case self::T_MONTH:
321 View Code Duplication
                case self::T_SA_MONTH:
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...
322
                    $month = $data[DateDefinitions::MONTH] + 1;
323
                    if ($count == 3) {
324
                        $out .= $locale->getCalendarMonthAbbreviated($calendarType, $month);
325
                    } elseif ($count == 4) {
326
                        $out .= $locale->getCalendarMonthWide($calendarType, $month);
327
                    } elseif ($count == 5) {
328
                        $out .= $locale->getCalendarMonthNarrow($calendarType, $month);
329
                    } else {
330
                        $out .= str_pad($month, $count, '0', STR_PAD_LEFT);
331
                    }
332
                    break;
333
334 View Code Duplication
                case self::T_DATE:
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...
335
                    $out .= str_pad($data[DateDefinitions::DATE], $count, '0', STR_PAD_LEFT);
336
                    break;
337
338 View Code Duplication
                case self::T_HOUR_1_24:
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...
339
                    $hour = $data[DateDefinitions::HOUR_OF_DAY];
340
                    $out .= str_pad($hour == 0 ? 24 : $hour, $count, '0', STR_PAD_LEFT);
341
                    break;
342
343 View Code Duplication
                case self::T_HOUR_0_23:
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...
344
                    $out .= str_pad($data[DateDefinitions::HOUR_OF_DAY], $count, '0', STR_PAD_LEFT);
345
                    break;
346
347 View Code Duplication
                case self::T_MINUTE:
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...
348
                    $out .= str_pad($data[DateDefinitions::MINUTE], $count, '0', STR_PAD_LEFT);
349
                    break;
350
351 View Code Duplication
                case self::T_SECOND:
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...
352
                    $out .= str_pad($data[DateDefinitions::SECOND], $count, '0', STR_PAD_LEFT);
353
                    break;
354
355 View Code Duplication
                case self::T_FRACTIONAL_SECOND:
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...
356
                    $value = str_pad($data[DateDefinitions::MILLISECOND], 3, '0', STR_PAD_LEFT);
357
                    $value = substr($value, 0, $count);
358
                    $out .= $value;
359
                    break;
360
361 View Code Duplication
                case self::T_DAY_OF_WEEK:
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...
362
                    $dow = $data[DateDefinitions::DAY_OF_WEEK];
363
                    if ($count == 4) {
364
                        $out .= $locale->getCalendarDayWide($calendarType, $dow);
365
                    } elseif ($count == 5) {
366
                        $out .= $locale->getCalendarDayNarrow($calendarType, $dow);
367
                    } else {
368
                        $out .= $locale->getCalendarDayAbbreviated($calendarType, $dow);
369
                    }
370
                    break;
371
372 View Code Duplication
                case self::T_DAY_OF_YEAR:
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...
373
                    $out .= str_pad($data[DateDefinitions::DAY_OF_YEAR], $count, '0', STR_PAD_LEFT);
374
                    break;
375
376 View Code Duplication
                case self::T_DAY_OF_WEEK_IN_MONTH:
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...
377
                    $out .= str_pad($data[DateDefinitions::DAY_OF_WEEK_IN_MONTH], $count, '0', STR_PAD_LEFT);
378
                    break;
379
380 View Code Duplication
                case self::T_WEEK_OF_YEAR:
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...
381
                    $out .= str_pad($data[DateDefinitions::WEEK_OF_YEAR], $count, '0', STR_PAD_LEFT);
382
                    break;
383
384 View Code Duplication
                case self::T_WEEK_OF_MONTH:
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...
385
                    $out .= str_pad($data[DateDefinitions::WEEK_OF_MONTH], $count, '0', STR_PAD_LEFT);
386
                    break;
387
388
                case self::T_AM_PM:
389
                    $isPm = $data[DateDefinitions::AM_PM];
390
                    if ($isPm) {
391
                        $out .= $locale->getCalendarPm($calendarType);
392
                    } else {
393
                        $out .= $locale->getCalendarAm($calendarType);
394
                    }
395
                    break;
396
397 View Code Duplication
                case self::T_HOUR_1_12:
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...
398
                    $hour = $data[DateDefinitions::HOUR];
399
                    $out .= str_pad($hour == 0 ? 12 : $hour, $count, '0', STR_PAD_LEFT);
400
                    break;
401
402 View Code Duplication
                case self::T_HOUR_0_11:
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...
403
                    $out .= str_pad($data[DateDefinitions::HOUR], $count, '0', STR_PAD_LEFT);
404
                    break;
405
406
                case self::T_TIMEZONE:
407
                case self::T_TIMEZONE_GENERIC:
408
                    if (!$tzid) {
409
                        $out .= $this->getGmtZoneString($data);
410
                    } else {
411
                        $displayString = '';
0 ignored issues
show
Unused Code introduced by
$displayString 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...
412
413
                        if ($token[0] == self::T_TIMEZONE_GENERIC) {
414
                            if ($count < 4) {
415
                                $displayString = $locale->getTimeZoneShortGenericName($tzid);
416
                            } else {
417
                                $displayString = $locale->getTimeZoneLongGenericName($tzid);
418
                            }
419
                            // if we don't have the generic data available
420
                            if (!$displayString && strlen($tzid) > 2 && strpos($tzid, '/') !== false && strncmp($tzid, 'Etc', 3) != 0) {
421
                                $hasMultiple = false;
422
                                $territory = $tm->getTimeZoneTerritory($tzid, $hasMultiple);
0 ignored issues
show
Unused Code introduced by
$territory 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...
423
                                // TODO: there are lots of rules in the ldml spec which could be covered here too
424
                                $displayString = $tzid;
425
                            }
426
                        } else {
427
                            if ($data[DateDefinitions::DST_OFFSET] != 0) {
428
                                if ($count < 4) {
429
                                    $displayString = $locale->getTimeZoneShortDaylightName($tzid);
430
                                } else {
431
                                    $displayString = $locale->getTimeZoneLongDaylightName($tzid);
432
                                }
433
                            } else {
434
                                if ($count < 4) {
435
                                    $displayString = $locale->getTimeZoneShortStandardName($tzid);
436
                                } else {
437
                                    $displayString = $locale->getTimeZoneLongStandardName($tzid);
438
                                }
439
                            }
440
                        }
441
442
                        if (!$displayString) {
443
                            $out .= $this->getGmtZoneString($data);
444
                        } else {
445
                            $out .= $displayString;
446
                        }
447
                    }
448
                    break;
449
450 View Code Duplication
                case self::T_ISO_YEAR:
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...
451
                    $out .= str_pad($data[DateDefinitions::YEAR_WOY], $count, '0', STR_PAD_LEFT);
452
                    break;
453
454
                case self::T_LOCAL_DAY_OF_WEEK:
455 View Code Duplication
                case self::T_SA_LOCAL_DAY_OF_WEEK:
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...
456
                    $dow = $data[DateDefinitions::DOW_LOCAL];
457
                    if ($count == 4) {
458
                        $out .= $locale->getCalendarDayWide($calendarType, $dow);
459
                    } elseif ($count == 5) {
460
                        $out .= $locale->getCalendarDayNarrow($calendarType, $dow);
461
                    } elseif ($count == 3) {
462
                        $out .= $locale->getCalendarDayAbbreviated($calendarType, $dow);
463
                    } else {
464
                        $out .= str_pad($dow, $count, '0', STR_PAD_LEFT);
465
                    }
466
                    break;
467
468 View Code Duplication
                case self::T_EXTENDED_YEAR:
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...
469
                    $out .= str_pad($data[DateDefinitions::EXTENDED_YEAR], $count, '0', STR_PAD_LEFT);
470
                    break;
471
472 View Code Duplication
                case self::T_MODIFIED_JD:
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...
473
                    $out .= str_pad($data[DateDefinitions::JULIAN_DAY], $count, '0', STR_PAD_LEFT);
474
                    break;
475
476 View Code Duplication
                case self::T_MS_IN_DAY:
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...
477
                    $out .= str_pad($data[DateDefinitions::MILLISECONDS_IN_DAY], $count, '0', STR_PAD_LEFT);
478
                    break;
479
480
                case self::T_TIMEZONE_RFC:
481
                    if ($count > 3) {
482
                        $out .= $this->getGmtZoneString($data);
483
                    } else {
484
                        $sign = '+';
485
486
                        $value = ($data[DateDefinitions::ZONE_OFFSET] + $data[DateDefinitions::DST_OFFSET]) / DateDefinitions::MILLIS_PER_MINUTE;
487
                        if ($value < 0) {
488
                            $value = -$value;
489
                            $sign = '-';
490
                        }
491
492
                        $value = ($value / 3) * 5 + ($value % 60); // minutes => KKmm
493
                        $out .= $sign . str_pad($value, 4, '0', STR_PAD_LEFT);
494
                    }
495
                    break;
496
497
                case self::T_QUARTER:
498
                case self::T_SA_QUARTER:
499
                    $quarter = (int)($data[DateDefinitions::MONTH] / 3);
500
                    if ($count == 3) {
501
                        $out .= $locale->getCalendarQuarterAbbreviated($calendarType, $quarter);
502
                    } elseif ($count == 4) {
503
                        $out .= $locale->getCalendarQuarterWide($calendarType, $quarter);
504
                    } else {
505
                        $out .= str_pad($quarter, $count, '0', STR_PAD_LEFT);
506
                    }
507
                    break;
508
            }
509
        }
510
511
        return $out;
512
    }
513
514
    /**
515
     * Returns the GMT-+hhmm string for a the offset given in data.
516
     *
517
     * @param      array $data Date information.
518
     *
519
     * @return     string The timezone string.
520
     *
521
     * @author     Dominik del Bondio <[email protected]>
522
     * @since      0.11.0
523
     */
524
    protected function getGmtZoneString(array $data)
525
    {
526
        $value = $data[DateDefinitions::ZONE_OFFSET] + $data[DateDefinitions::DST_OFFSET];
527
528
        if ($value < 0) {
529
            $str = 'GMT-';
530
            $value = -$value; // suppress the '-' sign for text display.
531
        } else {
532
            $str = 'GMT+';
533
        }
534
535
        $str .=     str_pad((int) ($value / DateDefinitions::MILLIS_PER_HOUR), 2, '0', STR_PAD_LEFT)
536
                        . ':'
537
                        . str_pad((int) (($value % DateDefinitions::MILLIS_PER_HOUR) / DateDefinitions::MILLIS_PER_MINUTE), 2, '0', STR_PAD_LEFT);
538
        return $str;
539
    }
540
541
    /**
542
     * Parses the format with the given character to token map.
543
     *
544
     * @param      string $format         The format to parse.
545
     * @param      array  $charToTokenMap The character to token map.
546
     *
547
     * @author     Dominik del Bondio <[email protected]>
548
     * @since      0.11.0
549
     */
550
    protected function internalParseFormat($format, array $charToTokenMap)
551
    {
552
        if ($this->originalFormatString == $format) {
553
            return;
554
        }
555
        $this->originalFormatString = $format;
556
557
        $this->tokenList = array();
558
        $tokenIdx = 0;
559
560
        $escapeStr = '';
561
562
        $inEscape = false;
563
        $fLen = strlen($format);
564
        for ($i = 0; $i < $fLen; ++$i) {
565
            $c = $format[$i];
566
            $cNext = ($i + 1 < $fLen) ? $format[$i+1] : '';
567
568
            if ($inEscape) {
569
                if ($c == '\'') {
570 View Code Duplication
                    if ('\'' == $cNext) {
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...
571
                        $escapeStr .= '\'';
572
                        ++$i;
573
                    } else {
574
                        $inEscape = false;
575
                        $this->tokenList[$tokenIdx] = array(self::T_TEXT, $escapeStr);
576
                        ++$tokenIdx;
577
                        $escapeStr = '';
578
                    }
579
                } else {
580
                    $escapeStr .= $c;
581
                }
582
            } else {
583
                if ($c == '\'') {
584 View Code Duplication
                    if ($cNext == '\'') {
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...
585
                        $this->tokenList[$tokenIdx] = array(self::T_TEXT, $c);
586
                        ++$tokenIdx;
587
                        ++$i;
588
                    } else {
589
                        $escapeStr = '';
590
                        $inEscape = true;
591
                    }
592
                } else {
593
                    if (preg_match('#[a-z]#i', $c)) {
594
                        if (isset($charToTokenMap[$c])) {
595
                            $tok = $charToTokenMap[$c];
596
                            if ($tokenIdx > 0 && $this->tokenList[$tokenIdx - 1][0] == $tok) {
597
                                // increase the tokencount if the last token was the same as this
598
                                ++$this->tokenList[$tokenIdx - 1][1];
599
                            } else {
600
                                $this->tokenList[$tokenIdx] = array($tok, 1);
601
                                ++$tokenIdx;
602
                            }
603
                        } else {
604
                            throw new \Agavi\Exception\AgaviException('Unknown pattern char "' . $c . '" (#'. ord($c) . ') in format string "' . $format . '" at index ' . $i);
605
                        }
606
                    } else {
607
                        $this->tokenList[$tokenIdx] = array(self::T_TEXT, $c);
608
                        ++$tokenIdx;
609
                    }
610
                }
611
            }
612
        }
613
        if ($escapeStr) {
614
            $this->tokenList[$tokenIdx] = array(self::T_TEXT, $escapeStr);
615
            ++$tokenIdx;
616
        }
617
    }
618
619
    /**
620
     * Parses a string into an calendar object. Note that this doesn't fallback to
621
     * the English locale.
622
     *
623
     * @param      string $dateString The string containing the date.
624
     * @param      mixed $locale The locale name or instance which should be used for
625
     *                   parsing local day, month, etc names.
626
     * @param      bool $strict Whether the parsing should be strict. (not allowing
627
     *                  numbers to exceed the defined length, not allowing missing
628
     *                  additional parts). The returned calendar object will be
629
     *                  non-lenient.
630
     *
631
     * @return     Calendar The calendar object.
632
     *
633
     * @author     Dominik del Bondio <[email protected]>
634
     * @since      0.11.0
635
     */
636
    public function parse($dateString, $locale = null, $strict = false)
637
    {
638 View Code Duplication
        if ($locale instanceof Locale) {
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...
639
            $tm = $locale->getContext()->getTranslationManager();
640
        } elseif ($this->context) {
641
            $tm = $this->context->getTranslationManager();
642
            if ($locale) {
643
                $locale = $tm->getLocale($locale);
644
            } else {
645
                $locale = $tm->getCurrentLocale();
646
            }
647
        } else {
648
            throw new \InvalidArgumentException('This DateFormat has not been initialize()d. To be able to pass a string as locale you need to call initialize() or create this DateFormat using TranslationManager::createDateFormat()');
649
        }
650
        
651
        $cal = $tm->createCalendar($locale);
652
        // we need to extract the era from the current date and set that
653
        // era after reinitialising the calendar because if this is not
654
        // done all dates would be BC instead of AD
655
        $era = $cal->get(DateDefinitions::ERA);
656
        $cal->clear();
657
        $cal->set(DateDefinitions::ERA, $era);
658
659
        if ($strict) {
660
            $cal->setLenient(false);
661
        }
662
663
        // TODO: let user chose calendar type when more calendars exist
664
        $calendarType = Calendar::GREGORIAN;
665
        $datePos = 0;
666
667
        $unprocessedTokens = array();
0 ignored issues
show
Unused Code introduced by
$unprocessedTokens 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...
668
669
        $tlCount = count($this->tokenList);
670
        for ($i = 0; $i < $tlCount; ++$i) {
671 View Code Duplication
            if ($datePos >= strlen($dateString)) {
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...
672
                if ($strict) {
673
                    throw new AgaviException('Input string "' . $dateString . '" is to short');
674
                }
675
                break;
676
            }
677
678
            $token = $this->tokenList[$i];
679
            $type = $this->getTokenType($token);
680
            // this and the next token are numbers
681
            if ($type == 'number' && $i + 1 < $tlCount && $this->getTokenType($this->tokenList[$i + 1]) == 'number') {
682
                $unprocessedTokens = array($token);
683
684
                // store all abutting numerical tokens for later processing
685
                do {
686
                    ++$i;
687
                    $unprocessedTokens[] = $this->tokenList[$i];
688
                } while ($i + 1 < $tlCount && $this->getTokenType($this->tokenList[$i + 1]) == 'number');
689
690
                // retrieve the amount of number characters at our current parse position
691
                $numberLen = strspn($dateString, '0123456789', $datePos);
692
693
                // calculate the length the numbers should have from the tokens
694
                $tokenReqLen = 0;
695
                foreach ($unprocessedTokens as $tk) {
696
                    $tokenReqLen += $tk[1];
697
                }
698
699
                // we mimic the ICU behaviour here which only decrements the count of
700
                // the first token (from ICU:  Take the pattern "HHmmss" as an example.
701
                // We will try to parse 2/2/2 characters of the input text, then if that
702
                // fails, 1/2/2.  We only adjust the width of the leftmost field; the
703
                // others remain fixed.)
704
                // I'm not sure why they are doing it that way since i think that
705
                // decrementing the next tokens when there still aren't enough numbers
706
                // wouldn't do any harm (ok, maybe the algorithm is a little harder to
707
                // implement ;) - Dominik
708
709
                $diff = $tokenReqLen - $numberLen;
710
                if ($diff > 0) {
711
                    // use a reference here so we can simply store the new token length into $tLen
712
                    $tLen =& $unprocessedTokens[0][1];
713
                    if ($diff >= $tLen - 1) {
714
                        $tLen -= $diff;
715
                    } else {
716
                        throw new AgaviException('Not enough digits given in "' . $dateString . '" at pos ' . $datePos . ' (expected ' . $tokenReqLen . ' digits)');
717
                    }
718
                }
719
720
                foreach ($unprocessedTokens as $token) {
721
                    $dateField = $this->getDateFieldFromTokenType($token[0]);
722
                    if ($dateField === null) {
723
                        throw new AgaviException('Token type ' . $token[0] . ' claims to be numerical but has no date field');
724
                    }
725
726
                    $number = (int) substr($dateString, $datePos, $token[1]);
727
728
                    $datePos += $token[1];
729
                    if ($dateField == DateDefinitions::HOUR_OF_DAY && $token[0] == DateFormat::T_HOUR_1_24 && $number == 24) {
730
                        $number = 0;
731 View Code Duplication
                    } elseif ($dateField == DateDefinitions::HOUR && $token[0] == DateFormat::T_HOUR_1_12 && $number == 12) {
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...
732
                        $number = 0;
733
                    } elseif ($dateField == DateDefinitions::MONTH && $number > 0) {
734
                        $number -= 1;
735
                    }
736
737 View Code Duplication
                    if (self::T_QUARTER == $token[0] || self::T_SA_QUARTER == $token[0]) {
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...
738
                        // only set the quarter if the date hasn't been set on the calendar object
739
                        if (!$cal->_isSet(DateDefinitions::MONTH)) {
740
                            $cal->set($dateField, ($number - 1) * 3);
741
                        }
742
                    } else {
743
                        $cal->set($dateField, $number);
744
                    }
745
                }
746
            } elseif ($type == 'number') {
747
                $numberLen = strspn($dateString, '0123456789', $datePos);
748
                $dateField = $this->getDateFieldFromTokenType($token[0]);
749
                if ($dateField === null) {
750
                    throw new AgaviException('Token type ' . $token[0] . ' claims to be numerical but has no date field');
751
                }
752
                if ($strict && $numberLen > 2 && $numberLen > $token[1]) {
753
                    $numberLen = $token[1];
754
                }
755
                if ($dateField == DateDefinitions::MILLISECOND && $numberLen > 3) {
756
                    // we only store in millisecond precision so only use the first 3 digits of the fractional second
757
                    $number = (int) substr($dateString, $datePos, 3);
758
                } else {
759
                    $number = (int) substr($dateString, $datePos, $numberLen);
760
                }
761
762
                $datePos += $numberLen;
763
                // since the month is 0-indexed in the calendar and 1-indexed in the
764
                // cldr we need to subtract 1 from the month
765
                if ($dateField == DateDefinitions::MONTH && $number > 0) {
766
                    $number -= 1;
767 View Code Duplication
                } elseif ($dateField == DateDefinitions::HOUR_OF_DAY && $token[0] == DateFormat::T_HOUR_1_24 && $number == 24) {
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...
768
                    $number = 0;
769
                } elseif ($dateField == DateDefinitions::HOUR && $token[0] == DateFormat::T_HOUR_1_12 && $number == 12) {
770
                    $number = 0;
771
                } elseif ($token[0] == self::T_YEAR && $token[1] == 2 && $numberLen <= 2) {
772
                    if ($number >= 40) {
773
                        $number += 1900;
774
                    } else {
775
                        $number += 2000;
776
                    }
777
                }
778
779 View Code Duplication
                if (self::T_QUARTER == $token[0] || self::T_SA_QUARTER == $token[0]) {
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...
780
                    // only set the quarter if the date hasn't been set on the calendar object
781
                    if (!$cal->_isSet(DateDefinitions::MONTH)) {
782
                        $cal->set($dateField, ($number - 1) * 3);
783
                    }
784
                } else {
785
                    $cal->set($dateField, $number);
786
                }
787
            } else { // $type == 'text'
788
                $count = $token[1];
789
                switch ($token[0]) {
790
                    case self::T_TEXT:
791
                        if (substr_compare($dateString, $token[1], $datePos, strlen($token[1])) == 0) {
792
                            $datePos += strlen($token[1]);
793
                        } elseif ($i + 1 == $tlCount && !$strict) {
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...
794
                            // when the last text token didn't match we don't do anything in non strict mode
795
                        } else {
796
                            throw new AgaviException('Unknown character in "' . $dateString . '" at pos ' . $datePos . ' (expected: "' . $token[1] . '", got: "' . substr($dateString, $datePos, strlen($token[1])) . '")');
797
                        }
798
                        break;
799
800
                    case self::T_ERA:
801
                    case self::T_DAY_OF_WEEK:
802
                    case self::T_LOCAL_DAY_OF_WEEK:
803
                    case self::T_SA_LOCAL_DAY_OF_WEEK:
804
                        $funcPrefix = '';
805
806
                        switch ($token[0]) {
807
                            case self::T_ERA:
808
                                $funcPrefix = 'getCalendarEras';
809
                                $dataField = DateDefinitions::ERA;
810
                                break;
811
                            case self::T_DAY_OF_WEEK:
812
                                $funcPrefix = 'getCalendarDays';
813
                                $dataField = DateDefinitions::DAY_OF_WEEK;
814
                                break;
815
816
                            case self::T_LOCAL_DAY_OF_WEEK:
817
                            case self::T_SA_LOCAL_DAY_OF_WEEK:
818
                                $funcPrefix = 'getCalendarDays';
819
                                $dataField = DateDefinitions::DOW_LOCAL;
820
                                break;
821
                        }
822
823
                        if ($count == 4) {
824
                            $items = $locale->{$funcPrefix . 'Wide'}($calendarType);
825
                        } elseif ($count == 5) {
826
                            $items = $locale->{$funcPrefix . 'Narrow'}($calendarType);
827
                        } else {
828
                            $items = $locale->{$funcPrefix . 'Abbreviated'}($calendarType);
829
                        }
830
                        $item = null;
831
                        if ($this->matchStringWithFallbacks($dateString, $items, $datePos, $item)) {
832
                            $cal->set($dataField, $item);
0 ignored issues
show
Bug introduced by
The variable $dataField 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...
833
                        } else {
834
                            throw new AgaviException('Unknown character in "' . $dateString . '" at pos ' . $datePos . ' (expected one of: ' . implode(', ', $items) . ')');
835
                        }
836
                        break;
837
838
                    case self::T_AM_PM:
839
                        $items = array(
840
                            0 => $locale->getCalendarAm($calendarType),
841
                            1 => $locale->getCalendarPm($calendarType),
842
                        );
843
844
                        $item = null;
845
                        if ($this->matchStringWithFallbacks($dateString, $items, $datePos, $item)) {
846
                            $cal->set(DateDefinitions::AM_PM, $item);
847
                        } else {
848
                            throw new AgaviException('Unknown character in "' . $dateString . '" at pos ' . $datePos . ' (expected one of: ' . implode(', ', $items) . ')');
849
                        }
850
                        break;
851
852
                    case self::T_TIMEZONE:
853
                    case self::T_TIMEZONE_GENERIC:
854
                    case self::T_TIMEZONE_RFC:
855
                        $remainder = substr($dateString, $datePos);
856
                        if (preg_match('#^(GMT)?(\+|-)?(\d{1,2}:\d{1,2}|\d{1,2}\d{1,2})#i', $remainder, $match)) {
857
                            $datePos += strlen($match[0]);
858
                            if (strtolower($match[1]) != 'gmt') {
859
                                $remainder = 'GMT' . $match[0];
860
                            } else {
861
                                $remainder = $match[0];
862
                            }
863
                            $tz = $tm->createTimeZone($remainder);
864
                        } else {
865
                            if ($i + 1 != $tlCount && preg_match('#^([a-z/]+)#i', $remainder, $match)) {
866
                                $remainder = $match[0];
867
                            }
868
                            if (!($tz = $tm->createTimeZone($remainder))) {
869
                                // try to match a localized timezone string
870
                                $z = 0;
871
                                $localizedTzMap = array();
872
                                $idToTzMap = array();
873
                                $tzNames = $locale->getTimeZoneNames();
874
                                foreach ($tzNames as $tzId => $tz) {
875
                                    foreach ($tz as $type => $names) {
876
                                        if (is_array($names)) {
877
                                            foreach ($names as $name) {
878
                                                $localizedTzMap[$z] = $name;
879
                                                $idToTzMap[$z] = $tzId;
880
                                                ++$z;
881
                                            }
882
                                        }
883
                                    }
884
                                }
885
886
                                $id = 0;
887
                                if ($this->matchStringWithFallbacks($dateString, $localizedTzMap, $datePos, $id)) {
888
                                    $tz = $tm->createTimeZone($idToTzMap[$id]);
889
                                } else {
890
                                    throw new AgaviException('Unknown character in "' . $dateString . '" at pos ' . $datePos . ' (expected one of: ' . implode(', ', $localizedTzMap) . ')');
891
                                }
892
                            } else {
893
                                $datePos += strlen($remainder);
894
                            }
895
                        }
896
897
                        $cal->setTimeZone($tz);
898
                        break;
899
900
                    case self::T_MONTH:
901 View Code Duplication
                    case self::T_SA_MONTH:
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...
902
                        if ($count == 3) {
903
                            $months = $locale->getCalendarMonthsAbbreviated($calendarType);
904
                        } elseif ($count == 4) {
905
                            $months = $locale->getCalendarMonthsWide($calendarType);
906
                        } elseif ($count == 5) {
907
                            $months = $locale->getCalendarMonthsNarrow($calendarType);
908
                        }
909
                        $month = null;
910
                        if ($this->matchStringWithFallbacks($dateString, $months, $datePos, $month)) {
0 ignored issues
show
Bug introduced by
The variable $months 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...
911
                            $cal->set(DateDefinitions::MONTH, $month - 1);
912
                        } else {
913
                            throw new AgaviException('Unknown character in "' . $dateString . '" at pos ' . $datePos . ' (expected one of: ' . implode(', ', $months) . ')');
914
                        }
915
916
                        break;
917
918
                    case self::T_QUARTER:
919 View Code Duplication
                    case self::T_SA_QUARTER:
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...
920
                        if ($count == 3) {
921
                            $quarters = $locale->getCalendarQuartersAbbreviated($calendarType);
922
                        } elseif ($count == 4) {
923
                            $quarters = $locale->getCalendarQuartersWide($calendarType);
924
                        } elseif ($count == 5) {
925
                            $quarters = $locale->getCalendarQuartersNarrow($calendarType);
926
                        }
927
                        $quarter = null;
928
                        if ($this->matchStringWithFallbacks($dateString, $quarters, $datePos, $quarter)) {
0 ignored issues
show
Bug introduced by
The variable $quarters 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...
929
                            if (!$cal->_isSet(DateDefinitions::MONTH)) {
930
                                $cal->set(DateDefinitions::MONTH, ($quarter - 1) * 3);
931
                            }
932
                        } else {
933
                            throw new AgaviException('Unknown character in "' . $dateString . '" at pos ' . $datePos . ' (expected one of: ' . implode(', ', $quarters) . ')');
934
                        }
935
                        break;
936
                }
937
            }
938
        }
939
940
        // make sure the calendar has it's time calculated,
941
        // so there aren't any strange effects when setting a new timezone
942
        // or a new date
943
        $cal->getTime();
944 View Code Duplication
        if ($strict) {
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...
945
            // calculate the time to get errors for invalid dates
946
            if ($datePos < strlen($dateString)) {
947
                throw new AgaviException('Input string "' . $dateString . '" has characters after the date');
948
            }
949
        }
950
951
        return $cal;
952
    }
953
954
    /**
955
     * Returns the date field which is associated with the given token.
956
     *
957
     * @param      int $type The type of the token.
958
     *
959
     * @return     int The date field in the calendar for this token type.
960
     *
961
     * @author     Dominik del Bondio <[email protected]>
962
     * @since      0.11.0
963
     */
964
    protected function getDateFieldFromTokenType($type)
965
    {
966
        static $typeMap = array(
967
            self::T_ERA                     => DateDefinitions::ERA,
968
            self::T_YEAR                    => DateDefinitions::YEAR,
969
            self::T_MONTH                   => DateDefinitions::MONTH,
970
            self::T_SA_MONTH                => DateDefinitions::MONTH,
971
            self::T_DATE                    => DateDefinitions::DATE,
972
            self::T_HOUR_1_24               => DateDefinitions::HOUR_OF_DAY,
973
            self::T_HOUR_0_23               => DateDefinitions::HOUR_OF_DAY,
974
            self::T_MINUTE                  => DateDefinitions::MINUTE,
975
            self::T_SECOND                  => DateDefinitions::SECOND,
976
            self::T_FRACTIONAL_SECOND       => DateDefinitions::MILLISECOND,
977
            self::T_DAY_OF_WEEK             => DateDefinitions::DAY_OF_WEEK,
978
            self::T_DAY_OF_YEAR             => DateDefinitions::DAY_OF_YEAR,
979
            self::T_DAY_OF_WEEK_IN_MONTH    => DateDefinitions::DAY_OF_WEEK_IN_MONTH,
980
            self::T_WEEK_OF_YEAR            => DateDefinitions::WEEK_OF_YEAR,
981
            self::T_WEEK_OF_MONTH           => DateDefinitions::WEEK_OF_MONTH,
982
            self::T_AM_PM                   => DateDefinitions::AM_PM,
983
            self::T_HOUR_1_12               => DateDefinitions::HOUR,
984
            self::T_HOUR_0_11               => DateDefinitions::HOUR,
985
            self::T_ISO_YEAR                => DateDefinitions::YEAR_WOY,
986
            self::T_LOCAL_DAY_OF_WEEK       => DateDefinitions::DOW_LOCAL,
987
            self::T_SA_LOCAL_DAY_OF_WEEK    => DateDefinitions::DOW_LOCAL,
988
            self::T_EXTENDED_YEAR           => DateDefinitions::EXTENDED_YEAR,
989
            self::T_MODIFIED_JD             => DateDefinitions::JULIAN_DAY,
990
            self::T_MS_IN_DAY               => DateDefinitions::MILLISECONDS_IN_DAY,
991
            self::T_QUARTER                 => DateDefinitions::MONTH,
992
            self::T_SA_QUARTER              => DateDefinitions::MONTH,
993
        );
994
995
        if (isset($typeMap[$type])) {
996
            return $typeMap[$type];
997
        } else {
998
            return null;
999
        }
1000
    }
1001
1002
    /**
1003
     * Returns whether a string matches any of the given possibilities at the
1004
     * current offset.
1005
     *
1006
     * @param      string $string The string to be matched in.
1007
     * @param      array  $possibilities The possibilities which can match.
1008
     * @param      int    $offset The offset to match at in the input string.
1009
     * @param      mixed  $matchedKey The key of the possibilities entry that matched.
1010
     *
1011
     * @return     bool Whether any possibility could be matched.
1012
     *
1013
     * @author     Dominik del Bondio <[email protected]>
1014
     * @since      0.11.0
1015
     */
1016
    protected function matchStringWithFallbacks($string, array $possibilities, &$offset, &$matchedKey)
1017
    {
1018
        $strlen = strlen($string);
1019
        // TODO: change this to match to longest match and not the first one.
1020
        foreach ($possibilities as $key => $possibility) {
1021
            $possLen = strlen($possibility);
1022
            // avoid warning when $string is not long enough for the possibility
1023
            if ($strlen >= ($offset + $possLen) && substr_compare($string, $possibility, $offset, $possLen) == 0) {
1024
                $offset += $possLen;
1025
                $matchedKey = $key;
1026
                return true;
1027
            }
1028
        }
1029
1030
        return false;
1031
    }
1032
1033
    /**
1034
     * Returns the type of a token
1035
     *
1036
     * @param      array $token The token.
1037
     *
1038
     * @return     string either 'text' or 'number'
1039
     *
1040
     * @author     Dominik del Bondio <[email protected]>
1041
     * @since      0.11.0
1042
     */
1043
    protected function getTokenType($token)
1044
    {
1045
        $type = 'text';
1046
        $tok = $token[0];
1047
        $cnt = $token[1];
1048
1049
        switch ($tok) {
1050
            case self::T_YEAR:
1051
            case self::T_DATE:
1052
            case self::T_HOUR_1_24:
1053
            case self::T_HOUR_0_23:
1054
            case self::T_HOUR_1_12:
1055
            case self::T_HOUR_0_11:
1056
            case self::T_MINUTE:
1057
            case self::T_SECOND:
1058
            case self::T_DAY_OF_YEAR:
1059
            case self::T_DAY_OF_WEEK_IN_MONTH:
1060
            case self::T_WEEK_OF_YEAR:
1061
            case self::T_WEEK_OF_MONTH:
1062
            case self::T_ISO_YEAR:
1063
            case self::T_EXTENDED_YEAR:
1064
            case self::T_MODIFIED_JD:
1065
            case self::T_MS_IN_DAY:
1066
            case self::T_FRACTIONAL_SECOND:
1067
                // number
1068
                $type = 'number';
1069
                break;
1070
1071
            case self::T_MONTH:
1072
            case self::T_SA_MONTH:
1073
            case self::T_LOCAL_DAY_OF_WEEK:
1074
            case self::T_SA_LOCAL_DAY_OF_WEEK:
1075
            case self::T_QUARTER:
1076
            case self::T_SA_QUARTER:
1077
                // string > 2
1078
                if ($cnt > 2) {
1079
                    $type = 'text';
1080
                } else {
1081
                    $type = 'number';
1082
                }
1083
                break;
1084
1085
            case self::T_TEXT:
1086
            case self::T_ERA:
1087
            case self::T_DAY_OF_WEEK:
1088
            case self::T_AM_PM:
1089
            case self::T_TIMEZONE:
1090
            case self::T_TIMEZONE_GENERIC:
1091
            case self::T_TIMEZONE_RFC:
1092
                $type = 'text';
1093
                // string
1094
                break;
1095
        }
1096
1097
        return $type;
1098
    }
1099
}
1100