Passed
Pull Request — master (#582)
by Richard
17:20
created

Time::localizeDatePicker()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.4742

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 12
nop 0
dl 0
loc 19
ccs 11
cts 15
cp 0.7332
crap 5.4742
rs 9.4888
c 0
b 0
f 0
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 46
    public static function cleanTime($time = null)
39
    {
40 46
        if (is_a($time, '\DateTime')) {
41 20
            return $time->setTimezone(Locale::getTimeZone());
42
        }
43 30
        if ($time === null || $time === 0 || $time === '') {
44 28
            return new \DateTime('now', Locale::getTimeZone());
45
        }
46 22
        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
The assignment to $parts is dead and can be removed.
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 + round($diff->f, 0);
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);
0 ignored issues
show
Deprecated Code introduced by
The function Punic\Plural::getRule() has been deprecated: Use getRuleOfType with a Plural::RULETYPE_CARDINAL type ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

123
                $rule = /** @scrutinizer ignore-deprecated */ Plural::getRule($value, $locale);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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 8
    public static function formatDate($value, $width = 'short', $toTimezone = '', $locale = '')
157
    {
158
        try {
159 8
            $formatted = Calendar::formatDateEx($value, $width, $toTimezone, $locale);
160
        } catch (\Punic\Exception $e) {
161
            \Xoops::getInstance()->events()->triggerEvent('core.exception', $e);
162
            $formatted = '';
163
        }
164 8
        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 8
    public static function formatTime($value, $width = 'short', $toTimezone = '', $locale = '')
186
    {
187
        try {
188 8
            $formatted = Calendar::formatTimeEx($value, $width, $toTimezone, $locale);
189
        } catch (\Punic\Exception $e) {
190
            \Xoops::getInstance()->events()->triggerEvent('core.exception', $e);
191
            $formatted = '';
192
        }
193 8
        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 4
    public static function localizeDatePicker()
223
    {
224 4
        $delimiter = '-';
225 4
        $locale = Locale::normalizeLocale(Locale::getCurrent(), $delimiter, false);
226 4
        if ('zh_Hant' === Locale::getCurrent()) {
227
            $locale = 'zh-TW';
228
        }
229 4
        if ($locale === 'zh') {
230
            $locale = 'zh-CN';
231
        }
232 4
        list($language) = explode($delimiter, $locale);
233 4
        $xoops = \Xoops::getInstance();
234
235 4
        $locales = array($locale, $language);
236 4
        foreach ($locales as $name) {
237 4
            $i18nScript = 'media/jquery/ui/i18n/datepicker-' . $name . '.js';
238 4
            if (file_exists($xoops->path($i18nScript))) {
239
                $xoops->theme()->addBaseScriptAssets($i18nScript);
0 ignored issues
show
Bug introduced by
$i18nScript of type string is incompatible with the type array expected by parameter $assets of Xoops\Core\Theme\XoopsTheme::addBaseScriptAssets(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

239
                $xoops->theme()->addBaseScriptAssets(/** @scrutinizer ignore-type */ $i18nScript);
Loading history...
240
                return;
241
            }
242
        }
243 4
    }
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 1
    protected static function utf8StringToChars($input)
253
    {
254 1
        $chars = array();
255 1
        $strLen = mb_strlen($input, 'UTF-8');
256 1
        for ($i = 0; $i < $strLen; $i++) {
257 1
            $chars[] = mb_substr($input, $i, 1, 'UTF-8');
258
        }
259 1
        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 1
    protected static function parseInputDate(\DateTime $datetime, $input, $locale = '')
274
    {
275 1
        $year = 0;
276 1
        $month = 0;
277 1
        $day = 0;
278
279 1
        $order = [];
280 1
        $dateFormat = Calendar::getDateFormat('short', $locale);
281 1
        $formatChars = static::utf8StringToChars($dateFormat);
282 1
        $state = 'non';
283 1
        $newstate = $state;
0 ignored issues
show
Unused Code introduced by
The assignment to $newstate is dead and can be removed.
Loading history...
284 1
        foreach ($formatChars as $char) {
285 1
            switch ($char) {
286 1
                case 'y':
287 1
                    $newstate = 'y';
288 1
                    break;
289 1
                case 'M':
290 1
                    $newstate = 'm';
291 1
                    break;
292 1
                case 'd':
293 1
                    $newstate = 'd';
294 1
                    break;
295
                default:
296 1
                    $newstate = 'non';
297 1
                    break;
298
            }
299 1
            if ($newstate !== $state) {
300 1
                if (in_array($newstate, ['y', 'm', 'd'])) {
301 1
                    $order[] = $newstate;
302
                }
303 1
                $state = $newstate;
304
            }
305
        }
306
307 1
        $pieces = [];
308 1
        $pieceIndex = -1;
309 1
        $inputChars = static::utf8StringToChars($input);
310 1
        $state = 'non';
311 1
        $newstate = $state;
312 1
        foreach ($inputChars as $char) {
313
            switch ($char) {
314 1
                case '0':
315 1
                case '1':
316 1
                case '2':
317 1
                case '3':
318 1
                case '4':
319 1
                case '5':
320 1
                case '6':
321 1
                case '7':
322 1
                case '8':
323 1
                case '9':
324 1
                    $newstate = 'digit';
325 1
                    break;
326
                default:
327 1
                    $newstate = 'non';
328 1
                    break;
329
            }
330 1
            if ($newstate !== $state) {
331 1
                if ($newstate === 'digit') {
332 1
                    $pieces[++$pieceIndex] = $char;
333
                }
334 1
                $state = $newstate;
335 1
            } elseif ($state === 'digit') {
336 1
                $pieces[$pieceIndex] .= $char;
337
            }
338
        }
339
340 1
        foreach ($pieces as $i => $piece) {
341 1
            $piece = (int) ltrim($piece, '0');
342 1
            switch ($order[$i]) {
343 1
                case 'd':
344 1
                    $day = $piece;
345 1
                    break;
346 1
                case 'm':
347 1
                    $month = $piece;
348 1
                    break;
349 1
                case 'y':
350 1
                    $year = $piece;
351 1
                    break;
352
            }
353
        }
354 1
        if ($year < 100) {
355
            if ($year<70) {
356
                $year += 2000;
357
            } else {
358
                $year += 1900;
359
            }
360
        }
361 1
        $datetime->setDate($year, $month, $day);
362
        // public DateTime DateTime::setTime ( int $hour , int $minute [, int $second = 0 ] )
363 1
    }
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 1
    protected static function parseInputTime(\DateTime $datetime, $input, $locale = '')
378
    {
379 1
        $timeFormat = Calendar::getTimeFormat('short', $locale);
380 1
        $am = Calendar::getDayperiodName('am', 'wide', $locale);
381 1
        $pm = Calendar::getDayperiodName('pm', 'wide', $locale);
382 1
        $clock12 = Calendar::has12HoursClock($locale);
383
384 1
        $hour = 0;
385 1
        $minute = 0;
386 1
        $second = 0;
387
388 1
        $order = [];
389 1
        $formatChars = static::utf8StringToChars($timeFormat);
390 1
        $state = 'non';
391 1
        $newstate = $state;
0 ignored issues
show
Unused Code introduced by
The assignment to $newstate is dead and can be removed.
Loading history...
392 1
        foreach ($formatChars as $char) {
393 1
            switch ($char) {
394 1
                case 'h':
395 1
                case 'H':
396 1
                    $newstate = 'h';
397 1
                    break;
398 1
                case 'm':
399 1
                    $newstate = 'm';
400 1
                    break;
401 1
                case 'a':
402
                default:
403 1
                    $newstate = 'non';
404 1
                    break;
405
            }
406 1
            if ($newstate !== $state) {
407 1
                if (in_array($newstate, ['h', 'm'])) {
408 1
                    $order[] = $newstate;
409
                }
410 1
                $state = $newstate;
411
            }
412
        }
413
414 1
        $pieces = [];
415 1
        $pieceIndex = -1;
416 1
        $inputChars = static::utf8StringToChars($input);
417 1
        $state = 'non';
418 1
        $newstate = $state;
419 1
        foreach ($inputChars as $char) {
420
            switch ($char) {
421 1
                case '0':
422 1
                case '1':
423 1
                case '2':
424 1
                case '3':
425 1
                case '4':
426 1
                case '5':
427 1
                case '6':
428 1
                case '7':
429 1
                case '8':
430 1
                case '9':
431 1
                    $newstate = 'digit';
432 1
                    break;
433
                default:
434 1
                    $newstate = 'non';
435 1
                    break;
436
            }
437 1
            if ($newstate !== $state) {
438 1
                if ($newstate === 'digit') {
439 1
                    $pieces[++$pieceIndex] = $char;
440
                }
441 1
                $state = $newstate;
442 1
            } elseif ($state === 'digit') {
443 1
                $pieces[$pieceIndex] .= $char;
444
            }
445
        }
446
447 1
        foreach ($pieces as $i => $piece) {
448 1
            $piece = (int) ltrim($piece, '0');
449 1
            switch ($order[$i]) {
450 1
                case 'h':
451 1
                    $hour = $piece;
452 1
                    break;
453 1
                case 'm':
454 1
                    $minute = $piece;
455 1
                    break;
456
            }
457
        }
458 1
        if ($clock12) {
459 1
            if ($hour == 12 && false !== mb_strpos($input, $am)) {
460 1
                $hour = 0;
461
            }
462 1
            if (false !== mb_strpos($input, $pm)) {
463
                $hour += 12;
464
            }
465
        }
466 1
        $datetime->setTime($hour, $minute, $second);
467 1
    }
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 1
    public static function inputToDateTime($input, $locale = '')
478
    {
479 1
        $dateTime = static::cleanTime();
480 1
        $dateTime->setTime(0, 0, 0);
481
482 1
        if (is_array($input)) {
483 1
            static::parseInputDate($dateTime, $input['date'], $locale);
484 1
            static::parseInputTime($dateTime, $input['time'], $locale);
485
        } else { // single string should be just a date
486 1
            static::parseInputDate($dateTime, $input, $locale);
487
        }
488 1
        return $dateTime;
489
    }
490
}
491