Dates   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 485
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 121
dl 0
loc 485
rs 10
c 0
b 0
f 0
wmc 24

34 Methods

Rating   Name   Duplication   Size   Complexity  
A getDay() 0 3 1
A getZone() 0 3 1
B hp$0 ➔ humanReadable() 0 34 7
A getHumanReadableFormats() 0 3 1
A setHumanReadableI18n() 0 3 1
A getYear() 0 3 1
A setHumanReadableI18nKey() 0 3 1
A getDate() 0 3 1
A lstTimeZone() 0 3 1
A getSqlFormat() 0 16 3
A lstTimeZoneCountries() 0 12 3
humanReadable() 0 34 ?
A getMinute() 0 3 1
A getHour() 0 3 1
A lstTimeZoneContinent() 0 13 1
A getSecond() 0 3 1
A getHumanReadableI18n() 0 3 1
A setHumanReadableFormats() 0 3 1
A getMonth() 0 3 1
A setHumanReadableFormatsKey() 0 5 1
A hp$0 ➔ humanDateYesterday() 0 9 1
humanDateOther() 0 16 ?
A hp$0 ➔ humanDateNow() 0 4 1
humanDateYesterday() 0 9 ?
B hp$0 ➔ humanDateIsYesterdayOrTomorrow() 0 37 6
humanDateNow() 0 4 ?
A hp$0 ➔ humanDateOther() 0 16 2
humanDateIsYesterdayOrTomorrow() 0 37 ?
A hp$0 ➔ humanDateTomorrow() 0 9 1
humanDateTomorrow() 0 9 ?
humanDateToday() 0 20 ?
A hp$0 ➔ humanDateToday() 0 20 5
humanParseDateAndTimeText() 0 10 ?
A hp$0 ➔ humanParseDateAndTimeText() 0 10 1
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
     * @var string[] $humanReadableI18n Words used in method to transform
16
     *  date difference to human readable.
17
     */
18
    protected static $humanReadableI18n = [
19
        'now'          => 'now',
20
        'today_past'   => '{time} ago',
21
        'today_future' => 'in {time}',
22
        'yesterday'    => 'yesterday',
23
        'tomorrow'     => 'tomorrow',
24
        'others'       => 'the {date}',
25
        'time_part'    => ' at {time}'
26
    ];
27
28
    /**
29
     * @var string[] $humanReadableFormats Date and time formats used in
30
     *  method to transform date difference to human readable.
31
     */
32
    protected static $humanReadableFormats = [
33
        'dateSameYear'      => 'm-d',
34
        'dateDifferentYear' => 'Y-m-d',
35
        'time'              => 'H:i'
36
    ];
37
38
    /**
39
     * Return the value of the humanReadableI18n property
40
     * 
41
     * @return string[]
42
     */
43
    public static function getHumanReadableI18n(): array
44
    {
45
        return self::$humanReadableI18n;
46
    }
47
48
    /**
49
     * Define a new value for a key of the humanReadableI18n property
50
     * 
51
     * @param string $key The key in humanReadableI18n
52
     * @param string $value The new value for the key
53
     * 
54
     * @return void
55
     */
56
    public static function setHumanReadableI18nKey(string $key, string $value)
57
    {
58
        self::$humanReadableI18n[$key] = $value;
59
    }
60
61
    /**
62
     * Define a new value to the property humanReadableI18n
63
     * 
64
     * @param string[] $value The new value for the property
65
     * 
66
     * @return void
67
     */
68
    public static function setHumanReadableI18n(array $value)
69
    {
70
        self::$humanReadableI18n = $value;
71
    }
72
73
    /**
74
     * Return the value of the humanReadableFormats property
75
     * 
76
     * @return string[]
77
     */
78
    public static function getHumanReadableFormats(): array
79
    {
80
        return self::$humanReadableFormats;
81
    }
82
83
    /**
84
     * Define a new value for a key of the humanReadableFormats property
85
     * 
86
     * @param string $key The key in humanReadableFormats
87
     * @param string $value The new value for the key
88
     * 
89
     * @return void
90
     */
91
    public static function setHumanReadableFormatsKey(
92
        string $key,
93
        string $value
94
    ) {
95
        self::$humanReadableFormats[$key] = $value;
96
    }
97
98
    /**
99
     * Define a new value to the property humanReadableFormats
100
     * 
101
     * @param string[] $value The new value for the property
102
     * 
103
     * @return void
104
     */
105
    public static function setHumanReadableFormats(array $value)
106
    {
107
        self::$humanReadableFormats = $value;
108
    }
109
110
    /**
111
     * Return the date. Format is Y-m-d H:i:sO
112
     * 
113
     * @return string
114
     */
115
    public function getDate(): string
116
    {
117
        return parent::format('Y-m-d H:i:sO');
118
    }
119
120
    /**
121
     * Return a numeric representation of a year, 4 digits.
122
     * 
123
     * @return int
124
     */
125
    public function getYear(): int
126
    {
127
        return (int) parent::format('Y');
128
    }
129
130
    /**
131
     * Return the numeric representation of a month, without leading zeros.
132
     * The returned int format can not have leading zeros.
133
     * 
134
     * @return int
135
     */
136
    public function getMonth(): int
137
    {
138
        return (int) parent::format('m');
139
    }
140
141
    /**
142
     * Return the day of the month without leading zeros.
143
     * The returned int format can not have leading zeros.
144
     * 
145
     * @return int
146
     */
147
    public function getDay(): int
148
    {
149
        return (int) parent::format('d');
150
    }
151
152
    /**
153
     * Return 24-hour format without leading zeros.
154
     * The returned int format can not have leading zeros.
155
     * 
156
     * @return int
157
     */
158
    public function getHour(): int
159
    {
160
        return (int) parent::format('H');
161
    }
162
163
    /**
164
     * Return minutes, without leading zeros.
165
     * The returned int format can not have leading zeros.
166
     * 
167
     * @return int
168
     */
169
    public function getMinute(): int
170
    {
171
        return (int) parent::format('i');
172
    }
173
174
    /**
175
     * Return second, without leading zeros.
176
     * The returned int format can not have leading zeros.
177
     * 
178
     * @return int
179
     */
180
    public function getSecond(): int
181
    {
182
        return (int) parent::format('s');
183
    }
184
185
    /**
186
     * Return the difference to Greenwich time (GMT)
187
     * with colon between hours and minutes
188
     * 
189
     * @return string
190
     */
191
    public function getZone(): string
192
    {
193
        return parent::format('P');
194
    }
195
    
196
    /**
197
     * Return date's SQL format (postgresql format).
198
     * The return can be an array or a string.
199
     * 
200
     * @param boolean $returnArray (default false) True to return an array.
201
     * @param boolean $withZone (default false) True to include the timezone
202
     *  into the time returned data.
203
     * 
204
     * @return string[]|string
205
     */
206
    public function getSqlFormat(
207
        bool $returnArray = false,
208
        bool $withZone = false
209
    ) {
210
        $date = $this->format('Y-m-d');
211
        $time = $this->format('H:i:s');
212
        
213
        if ($withZone === true) {
214
            $time .= $this->format('O');
215
        }
216
217
        if ($returnArray) {
218
            return [$date, $time];
219
        }
220
221
        return $date.' '.$time;
222
    }
223
224
    /**
225
     * List all timezone existing in current php version
226
     * 
227
     * @return string[]
228
     */
229
    public function lstTimeZone(): array
230
    {
231
        return parent::getTimezone()->listIdentifiers();
232
    }
233
234
    /**
235
     * List all continent define in php DateTimeZone.
236
     * 
237
     * @return string[]
238
     */
239
    public function lstTimeZoneContinent(): array
240
    {
241
        return [
242
            'Africa',
243
            'America',
244
            'Antartica',
245
            'Arctic',
246
            'Asia',
247
            'Atlantic',
248
            'Australia',
249
            'Europe',
250
            'Indian',
251
            'Pacific'
252
        ];
253
    }
254
255
    /**
256
     * List all available country for a continent
257
     * 
258
     * @param string $continent The continent for which we want
259
     *  the countries list
260
     * 
261
     * @return string[]
262
     */
263
    public function lstTimeZoneCountries(string $continent): array
264
    {
265
        $allCountries = $this->lstTimeZone();
266
        $countries    = [];
267
268
        foreach ($allCountries as $country) {
269
            if (strpos($country, $continent) !== false) {
270
                $countries[] = $country;
271
            }
272
        }
273
274
        return $countries;
275
    }
276
277
    /**
278
     * Transform a date to a human readable format
279
     * 
280
     * @param boolean $returnDateAndTime (default true) True to return date and
281
     *  time concatenated with a space. False to have only date.
282
     * 
283
     * @return string
284
     */
285
    public function humanReadable(bool $returnDateAndTime = true): string
286
    {
287
        $current = new Dates;
288
        $diff    = parent::diff($current);
289
        
290
        $parsedTxt = new class {
291
            public $date = '';
292
            public $time = '';
293
        };
294
295
        if ($current == $this) {
296
            //Now
297
            $this->humanDateNow($parsedTxt);
298
        } elseif (
299
            $this->humanDateIsYesterdayOrTomorrow($diff, $current) === true
0 ignored issues
show
Bug introduced by
It seems like $diff can also be of type false; however, parameter $diff of BFW\Helpers\Dates::human...IsYesterdayOrTomorrow() 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

299
            $this->humanDateIsYesterdayOrTomorrow(/** @scrutinizer ignore-type */ $diff, $current) === true
Loading history...
300
        ) {
301
            if ($diff->invert === 0) {
302
                $this->humanDateYesterday($parsedTxt); //Yesterday
303
            } else {
304
                $this->humanDateTomorrow($parsedTxt); //Tomorrow
305
            }
306
        } elseif ($diff->days === 0) {
307
            //Today
308
            $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

308
            $this->humanDateToday($parsedTxt, /** @scrutinizer ignore-type */ $diff);
Loading history...
309
        } else {
310
            $this->humanDateOther($parsedTxt, $current);
311
        }
312
313
        $txtReturned = $parsedTxt->date;
314
        if ($returnDateAndTime === true && $parsedTxt->time !== '') {
315
            $txtReturned .= $parsedTxt->time;
316
        }
317
318
        return $txtReturned;
319
    }
320
    
321
    /**
322
     * Check if the date to read for humanReadable is yesterday or tomorrow.
323
     * 
324
     * We cannot only check the property "d" of DateInterval because it's a
325
     * range of +/- 24 to 48 hours. If we are at 48h before, it's not ok.
326
     * 
327
     * @param \DateInterval $diff Interval between now and date to read
328
     * @param DateTime $current DateTime object for now
329
     * 
330
     * @return bool
331
     */
332
    protected function humanDateIsYesterdayOrTomorrow(
333
        \DateInterval $diff,
334
        \DateTime $current
335
    ): bool {
336
        //To check the range from 24h to 48h
337
        if (($diff->d === 1 && $diff->m === 0 && $diff->y === 0) === false) {
338
            return false;
339
        }
340
        
341
        /**
342
         * With $diff->d === 1, we know if we are in range from 24h to 48h.
343
         * But yesterday or tomorrow day can finish into the range.
344
         * 
345
         * Example :
346
         * 
347
         *    [---03/10---][---04/10---][---05/10---]
348
         * ---|----|-------|----|-------|----|------
349
         *       -48h         -24h         $this
350
         *         [ $diff->d=1 ]
351
         * 
352
         * Like we can see, the $diff->d=1 is not only on the 04/10, but also
353
         * on 03/10 because the range is from 24h to 48h before the date.
354
         * So we need a check to not display "yesterday" for the 03/10.
355
         */
356
        
357
        $twoDays = clone $current;
358
        if ($diff->invert === 0) {
359
            $twoDays->modify('-2 days');
360
        } else {
361
            $twoDays->modify('+2 days');
362
        }
363
        
364
        if ($this->format('d') === $twoDays->format('d')) {
365
            return false;
366
        }
367
        
368
        return true;
369
    }
370
    
371
    /**
372
     * Format date to human readable when the date is now
373
     * 
374
     * @param object $parsedTxt Texts returned by humanReadable method
375
     * 
376
     * @return void
377
     */
378
    protected function humanDateNow($parsedTxt)
379
    {
380
        $currentClass    = get_called_class();
381
        $parsedTxt->date = $currentClass::$humanReadableI18n['now'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
382
    }
383
    
384
    /**
385
     * Format date to human readable when date is today
386
     * 
387
     * @param object $parsedTxt Texts returned by humanReadable method
388
     * @param \DateInterval $diff Interval between now and date to read
389
     * 
390
     * @return void
391
     */
392
    protected function humanDateToday($parsedTxt, \DateInterval $diff)
393
    {
394
        $textKey = 'today_past';
395
        if ($diff->invert === 1) {
396
            $textKey = 'today_future';
397
        }
398
        
399
        $time = '';
400
        if ($diff->h === 0 && $diff->i === 0) {
401
            $time .= $diff->s.'s';
402
        } elseif ($diff->h === 0) {
403
            $time .= $diff->i.'min';
404
        } else {
405
            $time .= $diff->h.'h';
406
        }
407
        
408
        $currentClass    = get_called_class();
409
        $parsedTxt->date = $currentClass::$humanReadableI18n[$textKey];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
410
        
411
        $this->humanParseDateAndTimeText($parsedTxt, '', $time);
412
    }
413
    
414
    /**
415
     * Format date to human readable when date is yesterday
416
     * 
417
     * @param object $parsedTxt Texts returned by humanReadable method
418
     * 
419
     * @return void
420
     */
421
    protected function humanDateYesterday($parsedTxt)
422
    {
423
        $currentClass    = get_called_class();
424
        $parsedTxt->date = $currentClass::$humanReadableI18n['yesterday'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
425
        $parsedTxt->time = $currentClass::$humanReadableI18n['time_part'];
426
        
427
        $time = $this->format($currentClass::$humanReadableFormats['time']);
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
428
        
429
        $this->humanParseDateAndTimeText($parsedTxt, '', $time);
430
    }
431
    
432
    /**
433
     * Format date to human readable when date is tomorrow
434
     * 
435
     * @param object $parsedTxt Texts returned by humanReadable method
436
     * 
437
     * @return void
438
     */
439
    protected function humanDateTomorrow($parsedTxt)
440
    {
441
        $currentClass    = get_called_class();
442
        $parsedTxt->date = $currentClass::$humanReadableI18n['tomorrow'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
443
        $parsedTxt->time = $currentClass::$humanReadableI18n['time_part'];
444
        
445
        $time = $this->format($currentClass::$humanReadableFormats['time']);
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
446
        
447
        $this->humanParseDateAndTimeText($parsedTxt, '', $time);
448
    }
449
    
450
    /**
451
     * Format date to human readable when date is not now, today or yesterday
452
     * 
453
     * @param object $parsedTxt Texts returned by humanReadable method
454
     * @param \DateTime $current DateTime object for now
455
     * 
456
     * @return void
457
     */
458
    protected function humanDateOther($parsedTxt, \DateTime $current)
459
    {
460
        $currentClass = get_called_class();
461
        
462
        $dateFormat = $currentClass::$humanReadableFormats['dateDifferentYear'];
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
463
        if ($current->format('Y') === $this->format('Y')) {
464
            $dateFormat = $currentClass::$humanReadableFormats['dateSameYear'];
465
        }
466
        
467
        $parsedTxt->date = $currentClass::$humanReadableI18n['others'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
468
        $parsedTxt->time = $currentClass::$humanReadableI18n['time_part'];
469
        
470
        $date = $this->format($dateFormat);
471
        $time = $this->format($currentClass::$humanReadableFormats['time']);
472
        
473
        $this->humanParseDateAndTimeText($parsedTxt, $date, $time);
474
    }
475
    
476
    /**
477
     * Replace the expression "{date}" by the $date value and the expression
478
     * "{time}" by the $time value into properties "date" and "time" of the
479
     * $parsedTxt object.
480
     * 
481
     * @param object $parsedTxt Texts returned by humanReadable method
482
     * @param string $date The date value used to replace "{date}" into texts
483
     * @param string $time The time value used to replace "{time}" into texts
484
     * 
485
     * @return void
486
     */
487
    protected function humanParseDateAndTimeText(
488
        $parsedTxt,
489
        string $date,
490
        string $time
491
    ) {
492
        $parsedTxt->date = str_replace('{date}', $date, $parsedTxt->date);
493
        $parsedTxt->date = str_replace('{time}', $time, $parsedTxt->date);
494
        
495
        $parsedTxt->time = str_replace('{date}', $date, $parsedTxt->time);
496
        $parsedTxt->time = str_replace('{time}', $time, $parsedTxt->time);
497
    }
498
}
499