Passed
Push — 3.0 ( 1eb04a...9ca0ec )
by Vermeulen
02:07
created

Dates::setHumanReadableFormatsKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace BFW\Helpers;
4
5
use \DateTime;
6
use \Exception;
7
8
/**
9
 * Class to have shortcuts to DateTime(Zone) methods and to display a date
10
 * with words and not only numbers (today, yesterday, since... etc).
11
 */
12
class Dates extends DateTime
13
{
14
    /**
15
     * @const ERR_MODIFY_PATTERN_NOT_MATCH Exception code if the pattern used
16
     * into the method modify() not match with the regex.
17
     */
18
    const ERR_MODIFY_PATTERN_NOT_MATCH = 1605001;
19
    
20
    /**
21
     * @const ERR_MODIFY_UNKNOWN_MODIFIER Exception code if the modifier used
22
     * into the method modify() is unknown.
23
     */
24
    const ERR_MODIFY_UNKNOWN_MODIFIER = 1605002;
25
    
26
    /**
27
     * @var string[] $humanReadableI18n Words used in method to transform
28
     *  date difference to human readable.
29
     */
30
    protected static $humanReadableI18n = [
31
        'now'       => 'now',
32
        'since'     => 'since',
33
        'in'        => 'in',
34
        'yesterday' => 'yesterday',
35
        'tomorrow'  => 'tomorrow',
36
        'the'       => 'the',
37
        'at'        => 'at'
38
    ];
39
40
    /**
41
     * @var string[] $humanReadableFormats Date and time formats used in
42
     *  method to transform date difference to human readable.
43
     */
44
    protected static $humanReadableFormats = [
45
        'dateSameYear'      => 'm-d',
46
        'dateDifferentYear' => 'Y-m-d',
47
        'time'              => 'H:i'
48
    ];
49
    
50
    /**
51
     * @var string[] $modifyNewKeywords Add new keywords which can be used
52
     *  with the modify method. The key is the new keyword and the value the
53
     *  corresponding keyword into DateTime::modify method.
54
     */
55
    protected static $modifyNewKeywords = [];
56
57
    /**
58
     * Return the value of the humanReadableI18n property
59
     * 
60
     * @return string[]
61
     */
62
    public static function getHumanReadableI18n(): array
63
    {
64
        return self::$humanReadableI18n;
65
    }
66
67
    /**
68
     * Define a new value for a key of the humanReadableI18n property
69
     * 
70
     * @param string $key The key in humanReadableI18n
71
     * @param string $value The new value for the key
72
     * 
73
     * @return void
74
     */
75
    public static function setHumanReadableI18nKey(string $key, string $value)
76
    {
77
        self::$humanReadableI18n[$key] = $value;
78
    }
79
80
    /**
81
     * Define a new value to the property humanReadableI18n
82
     * 
83
     * @param string[] $value The new value for the property
84
     * 
85
     * @return void
86
     */
87
    public static function setHumanReadableI18n(array $value)
88
    {
89
        self::$humanReadableI18n = $value;
90
    }
91
92
    /**
93
     * Return the value of the humanReadableFormats property
94
     * 
95
     * @return string[]
96
     */
97
    public static function getHumanReadableFormats(): array
98
    {
99
        return self::$humanReadableFormats;
100
    }
101
102
    /**
103
     * Define a new value for a key of the humanReadableFormats property
104
     * 
105
     * @param string $key The key in humanReadableFormats
106
     * @param string $value The new value for the key
107
     * 
108
     * @return void
109
     */
110
    public static function setHumanReadableFormatsKey(
111
        string $key,
112
        string $value
113
    ) {
114
        self::$humanReadableFormats[$key] = $value;
115
    }
116
117
    /**
118
     * Define a new value to the property humanReadableFormats
119
     * 
120
     * @param string[] $value The new value for the property
121
     * 
122
     * @return void
123
     */
124
    public static function setHumanReadableFormats(array $value)
125
    {
126
        self::$humanReadableFormats = $value;
127
    }
128
    
129
    /**
130
     * Return the value of the modifyNewKeywords property
131
     * 
132
     * @return string[]
133
     */
134
    public static function getModifyNewKeywords(): array
135
    {
136
        return self::$modifyNewKeywords;
137
    }
138
    
139
    /**
140
     * Define a new value to the property modifyNewKeywords
141
     * 
142
     * @param string[] $value The new value for the property
143
     * 
144
     * @return void
145
     */
146
    public static function setModifyNewKeywords(array $value)
147
    {
148
        self::$modifyNewKeywords = $value;
149
    }
150
151
    /**
152
     * Return the date. Format is Y-m-d H:i:sO
153
     * 
154
     * @return string
155
     */
156
    public function getDate(): string
157
    {
158
        return parent::format('Y-m-d H:i:sO');
159
    }
160
161
    /**
162
     * Return a numeric representation of a year, 4 digits.
163
     * 
164
     * @return int
165
     */
166
    public function getYear(): int
167
    {
168
        return (int) parent::format('Y');
169
    }
170
171
    /**
172
     * Return the numeric representation of a month, without leading zeros.
173
     * The returned int format can not have leading zeros.
174
     * 
175
     * @return int
176
     */
177
    public function getMonth(): int
178
    {
179
        return (int) parent::format('m');
180
    }
181
182
    /**
183
     * Return the day of the month without leading zeros.
184
     * The returned int format can not have leading zeros.
185
     * 
186
     * @return int
187
     */
188
    public function getDay(): int
189
    {
190
        return (int) parent::format('d');
191
    }
192
193
    /**
194
     * Return 24-hour format without leading zeros.
195
     * The returned int format can not have leading zeros.
196
     * 
197
     * @return int
198
     */
199
    public function getHour(): int
200
    {
201
        return (int) parent::format('H');
202
    }
203
204
    /**
205
     * Return minutes, without leading zeros.
206
     * The returned int format can not have leading zeros.
207
     * 
208
     * @return int
209
     */
210
    public function getMinute(): int
211
    {
212
        return (int) parent::format('i');
213
    }
214
215
    /**
216
     * Return second, without leading zeros.
217
     * The returned int format can not have leading zeros.
218
     * 
219
     * @return int
220
     */
221
    public function getSecond(): int
222
    {
223
        return (int) parent::format('s');
224
    }
225
226
    /**
227
     * Return the difference to Greenwich time (GMT)
228
     * with colon between hours and minutes
229
     * 
230
     * @return string
231
     */
232
    public function getZone(): string
233
    {
234
        return parent::format('P');
235
    }
236
237
    /**
238
     * Override modify DateTime method to allow personal keywords
239
     * 
240
     * @param string $modify A date/time string
241
     * 
242
     * @return \BFW\Dates
0 ignored issues
show
Bug introduced by
The type BFW\Dates was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
243
     */
244
    public function modify($modify)
245
    {
246
        $originalDate = clone $this;
247
        @parent::modify($modify); //Try/catch on Throwable don't work T-T
248
249
        //If the keyword used is ok with DateTime::modify method
250
        if ($originalDate != $this) {
251
            return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type BFW\Helpers\Dates which is incompatible with the documented return type BFW\Dates.
Loading history...
252
        }
253
254
        $this->modifyWithOthersKeywords($modify);
255
256
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type BFW\Helpers\Dates which is incompatible with the documented return type BFW\Dates.
Loading history...
257
    }
258
259
    /**
260
     * Get DateTime equivalent keyword for a personal keyword declared into
261
     * the property modifyNewKeywords.
262
     * 
263
     * @return array
264
     */
265
    protected function obtainNewKeywordsForModify(): array
266
    {
267
        $search  = [];
268
        $replace = [];
269
        
270
        foreach (self::$modifyNewKeywords as $searchKey => $replaceKey) {
271
            $search[]  = $searchKey;
272
            $replace[] = $replaceKey;
273
        }
274
        
275
        return [
276
            'search'  => $search,
277
            'replace' => $replace
278
        ];
279
    }
280
    
281
    /**
282
     * Use personal keyword on modify method
283
     * 
284
     * @param string $modify A date/time string
285
     * 
286
     * @throws \Exception If bad pattern or unknown keyword
287
     * 
288
     * @return void
289
     */
290
    protected function modifyWithOthersKeywords(string $modify)
291
    {
292
        $keywords = $this->obtainNewKeywordsForModify();
293
        $match    = [];
294
        
295
        //Regex on the $modify parameter to get the used keyword
296
        if (preg_match('#(\+|\-)([0-9]+) ([a-z]+)#i', $modify, $match) !== 1) {
297
            throw new Exception(
298
                'Dates::modify pattern not match.',
299
                $this::ERR_MODIFY_PATTERN_NOT_MATCH
300
            );
301
        }
302
        
303
        $keyword = str_replace(
304
            $keywords['search'],
305
            $keywords['replace'],
306
            strtolower($match[3])
307
        );
308
        
309
        $originalDate = clone $this;
310
        //Try/catch on Throwable don't work T-T
311
        @parent::modify($match[1].$match[2].' '.$keyword);
312
        
313
        //If no change on object, The keyword is unknown
314
        if ($originalDate == $this) {
315
            throw new Exception(
316
                'Dates::modify Parameter '.$match[3].' is unknown.',
317
                $this::ERR_MODIFY_UNKNOWN_MODIFIER
318
            );
319
        }
320
    }
321
    
322
    /**
323
     * Return date's SQL format (postgresql format).
324
     * The return can be an array or a string.
325
     * 
326
     * @param boolean $returnArray (default false) True to return an array.
327
     * @param boolean $withZone (default false) True to include the timezone
328
     *  into the time returned data.
329
     * 
330
     * @return string[]|string
331
     */
332
    public function getSqlFormat(
333
        bool $returnArray = false,
334
        bool $withZone = false
335
    ) {
336
        $date = $this->format('Y-m-d');
337
        $time = $this->format('H:i:s');
338
        
339
        if ($withZone === true) {
340
            $time .= $this->format('O');
341
        }
342
343
        if ($returnArray) {
344
            return [$date, $time];
345
        }
346
347
        return $date.' '.$time;
348
    }
349
350
    /**
351
     * List all timezone existing in current php version
352
     * 
353
     * @return string[]
354
     */
355
    public function lstTimeZone(): array
356
    {
357
        return parent::getTimezone()->listIdentifiers();
358
    }
359
360
    /**
361
     * List all continent define in php DateTimeZone.
362
     * 
363
     * @return string[]
364
     */
365
    public function lstTimeZoneContinent(): array
366
    {
367
        return [
368
            'Africa',
369
            'America',
370
            'Antartica',
371
            'Arctic',
372
            'Asia',
373
            'Atlantic',
374
            'Australia',
375
            'Europe',
376
            'Indian',
377
            'Pacific'
378
        ];
379
    }
380
381
    /**
382
     * List all available country for a continent
383
     * 
384
     * @param string $continent The continent for which we want
385
     *  the countries list
386
     * 
387
     * @return string[]
388
     */
389
    public function lstTimeZoneCountries(string $continent): array
390
    {
391
        $allCountries = $this->lstTimeZone();
392
        $countries    = [];
393
394
        foreach ($allCountries as $country) {
395
            if (strpos($country, $continent) !== false) {
396
                $countries[] = $country;
397
            }
398
        }
399
400
        return $countries;
401
    }
402
403
    /**
404
     * Transform a date to a human readable format
405
     * 
406
     * @param boolean $returnDateAndTime (default true) True to return date and
407
     *  time concatenated with a space. False to have only date.
408
     * 
409
     * @return string
410
     */
411
    public function humanReadable(bool $returnDateAndTime = true): string
412
    {
413
        $current = new Dates;
414
        $diff    = parent::diff($current);
415
        
416
        $parsedTxt = new class {
417
            public $date = '';
418
            public $time = '';
419
        };
420
421
        if ($current == $this) {
422
            //Now
423
            $this->humanDateNow($parsedTxt);
424
        } elseif ($diff->d === 1 && $diff->m === 0 && $diff->y === 0) {
425
            if ($diff->invert === 0) {
426
                $this->humanDateYesterday($parsedTxt); //Yesterday
427
            } else {
428
                $this->humanDateTomorrow($parsedTxt); //Tomorrow
429
            }
430
        } elseif ($diff->days === 0) {
431
            //Today
432
            $this->humanDateToday($parsedTxt, $diff);
0 ignored issues
show
Bug introduced by
It seems like $diff can also be of type false; however, parameter $diff of BFW\Helpers\Dates::humanDateToday() does only seem to accept DateInterval, maybe add an additional type check? ( Ignorable by Annotation )

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

432
            $this->humanDateToday($parsedTxt, /** @scrutinizer ignore-type */ $diff);
Loading history...
433
        } else {
434
            $this->humanDateOther($parsedTxt, $current);
435
        }
436
437
        $txtReturned = $parsedTxt->date;
438
        if ($returnDateAndTime === true && $parsedTxt->time !== '') {
439
            $txtReturned .= ' '.$parsedTxt->time;
440
        }
441
442
        return $txtReturned;
443
    }
444
    
445
    /**
446
     * Format date to human readable when the date is now
447
     * 
448
     * @param object $parsedTxt Texts returned by humanReadable method
449
     * 
450
     * @return void
451
     */
452
    protected function humanDateNow($parsedTxt)
453
    {
454
        $currentClass    = get_called_class();
455
        $parsedTxt->date = $currentClass::$humanReadableI18n['now'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
456
    }
457
    
458
    /**
459
     * Format date to human readable when date is today
460
     * 
461
     * @param object $parsedTxt Texts returned by humanReadable method
462
     * @param \DateInterval $diff Interval between now and date to read
463
     * 
464
     * @return void
465
     */
466
    protected function humanDateToday($parsedTxt, \DateInterval $diff)
467
    {
468
        $textKey = 'since';
469
        if ($diff->invert === 1) {
470
            $textKey = 'in';
471
        }
472
        
473
        $currentClass    = get_called_class();
474
        $parsedTxt->date = $currentClass::$humanReadableI18n[$textKey].' ';
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
475
476
        if ($diff->h === 0 && $diff->i === 0) {
477
            $parsedTxt->date .= $diff->s.'s';
478
        } elseif ($diff->h === 0) {
479
            $parsedTxt->date .= $diff->i.'min';
480
        } else {
481
            $parsedTxt->date .= $diff->h.'h';
482
        }
483
    }
484
    
485
    /**
486
     * Format date to human readable when date is yesterday
487
     * 
488
     * @param object $parsedTxt Texts returned by humanReadable method
489
     * 
490
     * @return void
491
     */
492
    protected function humanDateYesterday($parsedTxt)
493
    {
494
        $currentClass    = get_called_class();
495
        $parsedTxt->date = $currentClass::$humanReadableI18n['yesterday'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
496
        $parsedTxt->time = $currentClass::$humanReadableI18n['at']
497
            .' '
498
            .$this->format(
499
                $currentClass::$humanReadableFormats['time']
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
500
            );
501
    }
502
    
503
    /**
504
     * Format date to human readable when date is tomorrow
505
     * 
506
     * @param object $parsedTxt Texts returned by humanReadable method
507
     * 
508
     * @return void
509
     */
510
    protected function humanDateTomorrow($parsedTxt)
511
    {
512
        $currentClass    = get_called_class();
513
        $parsedTxt->date = $currentClass::$humanReadableI18n['tomorrow'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
514
        $parsedTxt->time = $currentClass::$humanReadableI18n['at']
515
            .' '
516
            .$this->format(
517
                $currentClass::$humanReadableFormats['time']
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
518
            );
519
    }
520
    
521
    /**
522
     * Format date to human readable when date is not now, today or yesterday
523
     * 
524
     * @param object $parsedTxt Texts returned by humanReadable method
525
     * @param \DateTime $current DateTime object for now
526
     * 
527
     * @return void
528
     */
529
    protected function humanDateOther($parsedTxt, \DateTime $current)
530
    {
531
        $currentClass = get_called_class();
532
        
533
        $dateFormat = $currentClass::$humanReadableFormats['dateDifferentYear'];
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
534
        if ($current->format('Y') === $this->format('Y')) {
535
            $dateFormat = $currentClass::$humanReadableFormats['dateSameYear'];
536
        }
537
538
        $parsedTxt->date = $currentClass::$humanReadableI18n['the']
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
539
            .' '
540
            .$this->format($dateFormat);
541
542
        $parsedTxt->time = $currentClass::$humanReadableI18n['at']
543
            .' '
544
            .$this->format(
545
                $currentClass::$humanReadableFormats['time']
546
            );
547
    }
548
}
549