Completed
Pull Request — master (#489)
by Richard
10:39
created

Time::parseInputTime()   F

Complexity

Conditions 29
Paths 14400

Size

Total Lines 91
Code Lines 72

Duplication

Lines 48
Ratio 52.75 %

Code Coverage

Tests 0
CRAP Score 870

Importance

Changes 2
Bugs 1 Features 0
Metric Value
dl 48
loc 91
ccs 0
cts 69
cp 0
rs 2.0338
c 2
b 1
f 0
cc 29
eloc 72
nc 14400
nop 3
crap 870

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 You may not change or alter any portion of this comment or credits
4
 of supporting developers from this source code or any supporting source code
5
 which is considered copyrighted (c) material of the original comment or credit authors.
6
7
 This program is distributed in the hope that it will be useful,
8
 but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10
 */
11
12
namespace Xoops\Core\Locale;
13
14
use Xoops\Core\Locale\Punic\Calendar;
15
use Punic\Data;
16
use Punic\Plural;
17
use Xoops\Locale;
18
19
/**
20
 * Xoops\Core\Locale\Time - localized time handling
21
 *
22
 * @category  Xoops\Core\Locale\Time
23
 * @package   Xoops
24
 * @author    Richard Griffith <[email protected]>
25
 * @copyright 2015 XOOPS Project (http://xoops.org)/
26
 * @license   GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
27
 * @link      http://xoops.org
28
 */
29
class Time
30
{
31
    /**
32
     * cleanTime
33
     *
34
     * @param number|\DateTime|string $time An Unix timestamp, DateTime instance or string accepted by strtotime.
35
     *
36
     * @return \DateTime
37
     */
38 39
    public static function cleanTime($time = null)
39
    {
40 39
        if (is_a($time, '\DateTime')) {
41 17
            return $time->setTimezone(Locale::getTimeZone());
0 ignored issues
show
Bug introduced by
It seems like $time is not always an object, but can also be of type integer|double|string|null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
42
        }
43 23
        if ($time === null || $time === 0 || $time === '') {
44 22
            return new \DateTime('now', Locale::getTimeZone());
45
        }
46 21
        return Calendar::toDateTime($time, Locale::getTimeZone());
47
    }
48
49
    /**
50
     * Describe an relative interval from $dateStart to $dateEnd (eg '2 days ago').
51
     * Only the largest differing unit is described, and the next smaller unit will be used
52
     * for rounding.
53
     *
54
     * @param \DateTime      $dateEnd   The terminal date
55
     * @param \DateTime|null $dateStart The anchor date, defaults to now. (if it has a timezone different than
56
     *                        $dateEnd, we'll use the one of $dateEnd)
57
     * @param string         $width     The format name; it can be '', 'short' or 'narrow'
58
     * @param string         $locale    The locale to use. If empty we'll use the default locale set in \Punic\Data
59
     *
60
     * @return string
61
     *
62
     * @throws \InvalidArgumentException
63
     */
64 14
    public static function describeRelativeInterval($dateEnd, $dateStart = null, $width = '', $locale = '')
65
    {
66 14
        if (!is_a($dateEnd, '\DateTime')) {
67
            throw new \InvalidArgumentException('Not a DateTime object');
68
        }
69 14
        if (empty($dateStart) && ($dateStart !== 0) && ($dateStart !== '0')) {
70 14
            $dateStart = new \DateTime('now');
71
        } elseif (!is_a($dateStart, '\DateTime')) {
72
            throw new \InvalidArgumentException('Not a DateTime object');
73
        } else {
74
            $dateStart = clone $dateStart;
75
        }
76 14
        $dateStart->setTimezone($dateEnd->getTimezone());
77
78
        //$utc = new \DateTimeZone('UTC');
79
        //$dateEndUTC = new \DateTime($dateEnd->format('Y-m-d H:i:s'), $utc);
80
        //$dateStartUTC = new \DateTime($dateStart->format('Y-m-d H:i:s'), $utc);
81 14
        $parts = array();
0 ignored issues
show
Unused Code introduced by
$parts 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...
82 14
        $data = Data::get('dateFields', $locale);
83
84 14
        $diff = $dateStart->diff($dateEnd, false);
85 14
        $past = (boolean) $diff->invert;
86 14
        $value = 0;
87 14
        $key = '';
88 14
        if ($diff->y != 0) {
89 2
            $key = 'year';
90 2
            $value = $diff->y + (($diff->m > 6) ? 1 : 0);
91 12
        } elseif ($diff->m != 0) {
92
            $key = 'month';
93
            $value = $diff->m + (($diff->d > 15) ? 1 : 0);
94 12
        } elseif ($diff->d != 0) {
95 6
            $key = 'day';
96 6
            $value = $diff->d + (($diff->h > 12) ? 1 : 0);
97 6
        } elseif ($diff->h != 0) {
98
            $key = 'hour';
99
            $value = $diff->h + (($diff->i > 30) ? 1 : 0);
100 6
        } elseif ($diff->i != 0) {
101
            $key = 'minute';
102
            $value = $diff->i + (($diff->s > 30) ? 1 : 0);
103 6
        } elseif ($diff->s != 0) {
104 4
            $key = 'second';
105 4
            $value = $diff->s;
106
        }
107 14
        if ($value==0) {
108 2
            $key = 'second';
109 2
            $relKey = 'relative-type-0';
110 2
            $relPattern = null;
111 12
        } elseif ($key === 'day' && $value >1 && $value <7) {
112
            $dow = $dateEnd->format('N') - 1;
113
            $days = array('mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun');
114
            $key = $days[$dow];
115
            $relKey = ($past) ? "relative-type--1" : "relative-type-1";
116
            $relPattern = null;
117
        } else {
118 12
            if ($value == 1 && isset($data[$key]['relative-type--1'])) {
119 4
                $relKey = ($past) ? 'relative-type--1' : 'relative-type-1';
120 4
                $relPattern = null;
121
            } else {
122 8
                $relKey = ($past) ? 'relativeTime-type-past' : 'relativeTime-type-future';
123 8
                $rule = Plural::getRule($value, $locale);
124 8
                $relPattern = 'relativeTimePattern-count-' . $rule;
125
            }
126
        }
127 14
        if (!empty($width) && array_key_exists($key . '-' . $width, $data)) {
128
            $key .= '-' . $width;
129
        }
130 14
        if (empty($relPattern)) {
131 6
            $relativeString = $data[$key][$relKey];
132
        } else {
133 8
            $tempString = $data[$key][$relKey][$relPattern];
134 8
            $tempString = str_replace('{0}', '%d', $tempString);
135 8
            $relativeString = sprintf($tempString, $value);
136
        }
137 14
        return $relativeString;
138
    }
139
140
    /**
141
     * Format a date.
142
     *
143
     * @param number|\DateTime|string $value      An Unix timestamp, a `\DateTime` instance or a string accepted
144
     *                                             by strtotime().
145
     * @param string                  $width      The format name; it can be
146
     *                                               'full' (eg 'EEEE, MMMM d, y' - 'Wednesday, August 20, 2014'),
147
     *                                               'long' (eg 'MMMM d, y' - 'August 20, 2014'),
148
     *                                               'medium' (eg 'MMM d, y' - 'August 20, 2014') or
149
     *                                               'short' (eg 'M/d/yy' - '8/20/14').
150
     * @param string|\DateTimeZone    $toTimezone The timezone to set; leave empty to use the default timezone
151
     *                                             (or the timezone associated to $value if it's already a \DateTime)
152
     * @param string                  $locale     The locale to use. If empty we'll use the default
153
     *
154
     * @return string Returns an empty string if $value is empty, the localized textual representation otherwise
155
     */
156 4
    public static function formatDate($value, $width = 'short', $toTimezone = '', $locale = '')
157
    {
158
        try {
159 4
            $formatted = Calendar::formatDateEx($value, $width, $toTimezone, $locale);
160
        } catch (\Punic\Exception $e) {
0 ignored issues
show
Bug introduced by
The class Punic\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
161
            \Xoops::getInstance()->events()->triggerEvent('core.exception', $e);
162
            $formatted = '';
163
        }
164 4
        return $formatted;
165
    }
166
167
    /**
168
     * Format a date.
169
     *
170
     * @param number|\DateTime|string $value      An Unix timestamp, a `\DateTime` instance or a string accepted
171
     *                                             by strtotime().
172
     * @param string                  $width      The format name; it can be
173
     *                                               'full' (eg 'h:mm:ss a zzzz' - '11:42:13 AM GMT+2:00'),
174
     *                                               'long' (eg 'h:mm:ss a z' - '11:42:13 AM GMT+2:00'),
175
     *                                               'medium' (eg 'h:mm:ss a' - '11:42:13 AM') or
176
     *                                               'short' (eg 'h:mm a' - '11:42 AM')
177
     * @param string|\DateTimeZone    $toTimezone The timezone to set; leave empty to use the default timezone
178
     *                                             (or the timezone associated to $value if it's already a \DateTime)
179
     * @param string                  $locale     The locale to use. If empty we'll use the default
180
     *
181
     * @return string Returns an empty string if $value is empty, the localized textual representation otherwise
182
     *
183
     * @throws \Punic\Exception Throws an exception in case of problems
184
     */
185 5
    public static function formatTime($value, $width = 'short', $toTimezone = '', $locale = '')
186
    {
187
        try {
188 5
            $formatted = Calendar::formatTimeEx($value, $width, $toTimezone, $locale);
189
        } catch (\Punic\Exception $e) {
0 ignored issues
show
Bug introduced by
The class Punic\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
190
            \Xoops::getInstance()->events()->triggerEvent('core.exception', $e);
191
            $formatted = '';
192
        }
193 5
        return $formatted;
194
    }
195
196
    /**
197
     * Format a date/time.
198
     *
199
     * @param \DateTime $value The \DateTime instance for which you want the localized textual representation
200
     * @param string    $width The format name; it can be 'full', 'long', 'medium', 'short' or a combination
201
     *                          for date+time like 'full|short' or a combination for format+date+time like
202
     *                          'full|full|short'
203
     *                          You can also append an asterisk ('*') to the date part of $width. If so,
204
     *                          special day names may be used (like 'Today', 'Yesterday', 'Tomorrow') instead
205
     *                          of the date part.
206
     * @param string $locale   The locale to use. If empty we'll use the default locale
207
     *
208
     * @return string Returns an empty string if $value is empty, the localized textual representation otherwise
209
     *
210
     * @throws \Punic\Exception Throws an exception in case of problems
211
     */
212 8
    public static function formatDateTime(\DateTime $value, $width, $locale = '')
213
    {
214 8
        return Calendar::formatDatetime($value, $width, $locale);
215
    }
216
217
    /**
218
     * Perform any localization required for date picker used in Form\DateSelect
219
     *
220
     * @return void
221
     */
222
    public static function localizeDatePicker()
223
    {
224
        $delimiter = '-';
225
        $locale = Locale::normalizeLocale(Locale::getCurrent(), $delimiter, false);
226
        if ('zh_Hant' === Locale::getCurrent()) {
227
            $locale = 'zh-TW';
228
        }
229
        if ($locale === 'zh') {
230
            $locale = 'zh-CN';
231
        }
232
        list($language) = explode($delimiter, $locale);
233
        $xoops = \Xoops::getInstance();
234
235
        $locales = array($locale, $language);
236
        foreach ($locales as $name) {
237
            $i18nScript = 'media/jquery/ui/i18n/datepicker-' . $name . '.js';
238
            if (file_exists($xoops->path($i18nScript))) {
239
                $xoops->theme()->addBaseScriptAssets($i18nScript);
240
                return;
241
            }
242
        }
243
    }
244
245
    /**
246
     * turn a utf8 string into an array of characters
247
     *
248
     * @param string $input string to convert
249
     *
250
     * @return array
251
     */
252
    protected static function utf8StringToChars($input)
253
    {
254
        $chars = array();
255
        $strLen = mb_strlen($input, 'UTF-8');
256
        for ($i = 0; $i < $strLen; $i++) {
257
            $chars[] = mb_substr($input, $i, 1, 'UTF-8');
258
        }
259
        return $chars;
260
    }
261
262
    /**
263
     * parse a date input according to a locale and apply it to a DateTime object
264
     *
265
     * @param \DateTime $datetime datetime to apply date to
266
     * @param string    $input    localized date string
267
     * @param string    $locale   optional locale to use, leave blank to use current
268
     *
269
     * @return void
270
     *
271
     * @throws \Punic\Exception\ValueNotInList
272
     */
273
    protected static function parseInputDate(\DateTime $datetime, $input, $locale = '')
274
    {
275
        $year = 0;
276
        $month = 0;
277
        $day = 0;
278
279
        $order = [];
280
        $dateFormat = Calendar::getDateFormat('short', $locale);
281
        $formatChars = static::utf8StringToChars($dateFormat);
282
        $state = 'non';
283
        $newstate = $state;
0 ignored issues
show
Unused Code introduced by
$newstate 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...
284 View Code Duplication
        foreach ($formatChars as $char) {
285
            switch ($char) {
286
                case 'y':
287
                    $newstate = 'y';
288
                    break;
289
                case 'M':
290
                    $newstate = 'm';
291
                    break;
292
                case 'd':
293
                    $newstate = 'd';
294
                    break;
295
                default:
296
                    $newstate = 'non';
297
                    break;
298
            }
299
            if ($newstate !== $state) {
300
                if (in_array($newstate, ['y', 'm', 'd'])) {
301
                    $order[] = $newstate;
302
                }
303
                $state = $newstate;
304
            }
305
        }
306
307
        $pieces = [];
308
        $pieceIndex = -1;
309
        $inputChars = static::utf8StringToChars($input);
310
        $state = 'non';
311
        $newstate = $state;
0 ignored issues
show
Unused Code introduced by
$newstate 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...
312 View Code Duplication
        foreach ($inputChars as $char) {
313
            switch ($char) {
314
                case '0':
315
                case '1':
316
                case '2':
317
                case '3':
318
                case '4':
319
                case '5':
320
                case '6':
321
                case '7':
322
                case '8':
323
                case '9':
324
                    $newstate = 'digit';
325
                    break;
326
                default:
327
                    $newstate = 'non';
328
                    break;
329
            }
330
            if ($newstate !== $state) {
331
                if ($newstate === 'digit') {
332
                    $pieces[++$pieceIndex] = $char;
333
                }
334
                $state = $newstate;
335
            } elseif ($state === 'digit') {
336
                $pieces[$pieceIndex] .= $char;
337
            }
338
        }
339
340
        foreach ($pieces as $i => $piece) {
341
            $piece = (int) ltrim($piece, '0');
342
            switch ($order[$i]) {
343
                case 'd':
344
                    $day = $piece;
345
                    break;
346
                case 'm':
347
                    $month = $piece;
348
                    break;
349
                case 'y':
350
                    $year = $piece;
351
                    break;
352
            }
353
        }
354
        if ($year < 100) {
355
            if ($year<70) {
356
                $year += 2000;
357
            } else {
358
                $year += 1900;
359
            }
360
        }
361
        $datetime->setDate($year, $month, $day);
362
        // public DateTime DateTime::setTime ( int $hour , int $minute [, int $second = 0 ] )
363
    }
364
365
    /**
366
     * parse a time input according to a locale and apply it to a DateTime object
367
     *
368
     * @param \DateTime $datetime datetime to apply time to
369
     * @param string    $input    localized time string
370
     * @param string    $locale   optional locale to use, leave blank to use current
371
     *
372
     * @return void
373
     *
374
     * @throws \Punic\Exception\BadArgumentType
375
     * @throws \Punic\Exception\ValueNotInList
376
     */
377
    protected static function parseInputTime(\DateTime $datetime, $input, $locale = '')
378
    {
379
        $timeFormat = Calendar::getTimeFormat('short', $locale);
380
        $am = Calendar::getDayperiodName('am', 'wide', $locale);
381
        $pm = Calendar::getDayperiodName('pm', 'wide', $locale);
382
        $clock12 = Calendar::has12HoursClock($locale);
383
384
        $hour = 0;
385
        $minute = 0;
386
        $second = 0;
387
388
        $order = [];
389
        $formatChars = static::utf8StringToChars($timeFormat);
390
        $state = 'non';
391
        $newstate = $state;
0 ignored issues
show
Unused Code introduced by
$newstate 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...
392 View Code Duplication
        foreach ($formatChars as $char) {
393
            switch ($char) {
394
                case 'h':
395
                case 'H':
396
                    $newstate = 'h';
397
                    break;
398
                case 'm':
399
                    $newstate = 'm';
400
                    break;
401
                case 'a':
402
                default:
403
                    $newstate = 'non';
404
                    break;
405
            }
406
            if ($newstate !== $state) {
407
                if (in_array($newstate, ['h', 'm'])) {
408
                    $order[] = $newstate;
409
                }
410
                $state = $newstate;
411
            }
412
        }
413
414
        $pieces = [];
415
        $pieceIndex = -1;
416
        $inputChars = static::utf8StringToChars($input);
417
        $state = 'non';
418
        $newstate = $state;
0 ignored issues
show
Unused Code introduced by
$newstate 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...
419 View Code Duplication
        foreach ($inputChars as $char) {
420
            switch ($char) {
421
                case '0':
422
                case '1':
423
                case '2':
424
                case '3':
425
                case '4':
426
                case '5':
427
                case '6':
428
                case '7':
429
                case '8':
430
                case '9':
431
                    $newstate = 'digit';
432
                    break;
433
                default:
434
                    $newstate = 'non';
435
                    break;
436
            }
437
            if ($newstate !== $state) {
438
                if ($newstate === 'digit') {
439
                    $pieces[++$pieceIndex] = $char;
440
                }
441
                $state = $newstate;
442
            } elseif ($state === 'digit') {
443
                $pieces[$pieceIndex] .= $char;
444
            }
445
        }
446
447
        foreach ($pieces as $i => $piece) {
448
            $piece = (int) ltrim($piece, '0');
449
            switch ($order[$i]) {
450
                case 'h':
451
                    $hour = $piece;
452
                    break;
453
                case 'm':
454
                    $minute = $piece;
455
                    break;
456
            }
457
        }
458
        if ($clock12) {
459
            if ($hour == 12 && false !== mb_strpos($input, $am)) {
460
                $hour = 0;
461
            }
462
            if (false !== mb_strpos($input, $pm)) {
463
                $hour += 12;
464
            }
465
        }
466
        $datetime->setTime($hour, $minute, $second);
467
    }
468
469
    /**
470
     * Convert a XOOPS DateSelect or DateTime form input into a DateTime object
471
     *
472
     * @param string|string[] $input  date string, or array of date and time strings
473
     * @param string          $locale optional locale to use, leave blank to use current
474
     *
475
     * @return \DateTime
476
     */
477
    public static function inputToDateTime($input, $locale = '')
478
    {
479
        $dateTime = static::cleanTime();
480
        $dateTime->setTime(0, 0, 0);
481
482
        if (is_array($input)) {
483
            static::parseInputDate($dateTime, $input['date'], $locale);
484
            static::parseInputTime($dateTime, $input['time'], $locale);
485
        } else { // single string should be just a date
486
            static::parseInputDate($dateTime, $input, $locale);
487
        }
488
        return $dateTime;
489
    }
490
}
491