Completed
Push — 3.0 ( 5276e1...94e02b )
by Vermeulen
01:25
created

Dates   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 474
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 121
dl 0
loc 474
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 hp$0 ➔ humanDateYesterday() 0 9 1
A setHumanReadableI18n() 0 3 1
A getYear() 0 3 1
humanDateOther() 0 16 ?
A setHumanReadableI18nKey() 0 3 1
A hp$0 ➔ humanDateNow() 0 4 1
humanDateYesterday() 0 9 ?
A getDate() 0 3 1
B hp$0 ➔ humanDateIsYesterdayOrTomorrow() 0 37 6
A lstTimeZone() 0 3 1
humanDateNow() 0 4 ?
A getSqlFormat() 0 16 3
A lstTimeZoneCountries() 0 12 3
A hp$0 ➔ humanDateOther() 0 16 2
humanReadable() 0 34 ?
humanDateIsYesterdayOrTomorrow() 0 37 ?
A hp$0 ➔ humanDateTomorrow() 0 9 1
humanDateTomorrow() 0 9 ?
humanDateToday() 0 20 ?
A getMinute() 0 3 1
A getHour() 0 3 1
A hp$0 ➔ humanDateToday() 0 20 5
A lstTimeZoneContinent() 0 13 1
humanParseDateAndTimeText() 0 10 ?
A getSecond() 0 3 1
A getHumanReadableI18n() 0 3 1
A hp$0 ➔ humanParseDateAndTimeText() 0 10 1
A setHumanReadableFormats() 0 3 1
A getMonth() 0 3 1
A setHumanReadableFormatsKey() 0 5 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
    protected function humanDateIsYesterdayOrTomorrow(
322
        \DateInterval $diff,
323
        \DateTime $current
324
    ): bool {
325
        //To check the range from 24h to 48h
326
        if (($diff->d === 1 && $diff->m === 0 && $diff->y === 0) === false) {
327
            return false;
328
        }
329
        
330
        /**
331
         * With $diff->d === 1, we know if we are in range from 24h to 48h.
332
         * But yesterday or tomorrow day can finish into the range.
333
         * 
334
         * Example :
335
         * 
336
         *    [---03/10---][---04/10---][---05/10---]
337
         * ---|----|-------|----|-------|----|------
338
         *       -48h         -24h         $this
339
         *         [ $diff->d=1 ]
340
         * 
341
         * Like we can see, the $diff->d=1 is not only on the 04/10, but also
342
         * on 03/10 because the range is from 24h to 48h before the date.
343
         * So we need a check to not display "yesterday" for the 03/10.
344
         */
345
        
346
        $twoDays = clone $current;
347
        if ($diff->invert === 0) {
348
            $twoDays->modify('-2 days');
349
        } else {
350
            $twoDays->modify('+2 days');
351
        }
352
        
353
        if ($this->format('d') === $twoDays->format('d')) {
354
            return false;
355
        }
356
        
357
        return true;
358
    }
359
    
360
    /**
361
     * Format date to human readable when the date is now
362
     * 
363
     * @param object $parsedTxt Texts returned by humanReadable method
364
     * 
365
     * @return void
366
     */
367
    protected function humanDateNow($parsedTxt)
368
    {
369
        $currentClass    = get_called_class();
370
        $parsedTxt->date = $currentClass::$humanReadableI18n['now'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
371
    }
372
    
373
    /**
374
     * Format date to human readable when date is today
375
     * 
376
     * @param object $parsedTxt Texts returned by humanReadable method
377
     * @param \DateInterval $diff Interval between now and date to read
378
     * 
379
     * @return void
380
     */
381
    protected function humanDateToday($parsedTxt, \DateInterval $diff)
382
    {
383
        $textKey = 'today_past';
384
        if ($diff->invert === 1) {
385
            $textKey = 'today_future';
386
        }
387
        
388
        $time = '';
389
        if ($diff->h === 0 && $diff->i === 0) {
390
            $time .= $diff->s.'s';
391
        } elseif ($diff->h === 0) {
392
            $time .= $diff->i.'min';
393
        } else {
394
            $time .= $diff->h.'h';
395
        }
396
        
397
        $currentClass    = get_called_class();
398
        $parsedTxt->date = $currentClass::$humanReadableI18n[$textKey];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
399
        
400
        $this->humanParseDateAndTimeText($parsedTxt, '', $time);
401
    }
402
    
403
    /**
404
     * Format date to human readable when date is yesterday
405
     * 
406
     * @param object $parsedTxt Texts returned by humanReadable method
407
     * 
408
     * @return void
409
     */
410
    protected function humanDateYesterday($parsedTxt)
411
    {
412
        $currentClass    = get_called_class();
413
        $parsedTxt->date = $currentClass::$humanReadableI18n['yesterday'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
414
        $parsedTxt->time = $currentClass::$humanReadableI18n['time_part'];
415
        
416
        $time = $this->format($currentClass::$humanReadableFormats['time']);
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
417
        
418
        $this->humanParseDateAndTimeText($parsedTxt, '', $time);
419
    }
420
    
421
    /**
422
     * Format date to human readable when date is tomorrow
423
     * 
424
     * @param object $parsedTxt Texts returned by humanReadable method
425
     * 
426
     * @return void
427
     */
428
    protected function humanDateTomorrow($parsedTxt)
429
    {
430
        $currentClass    = get_called_class();
431
        $parsedTxt->date = $currentClass::$humanReadableI18n['tomorrow'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
432
        $parsedTxt->time = $currentClass::$humanReadableI18n['time_part'];
433
        
434
        $time = $this->format($currentClass::$humanReadableFormats['time']);
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
435
        
436
        $this->humanParseDateAndTimeText($parsedTxt, '', $time);
437
    }
438
    
439
    /**
440
     * Format date to human readable when date is not now, today or yesterday
441
     * 
442
     * @param object $parsedTxt Texts returned by humanReadable method
443
     * @param \DateTime $current DateTime object for now
444
     * 
445
     * @return void
446
     */
447
    protected function humanDateOther($parsedTxt, \DateTime $current)
448
    {
449
        $currentClass = get_called_class();
450
        
451
        $dateFormat = $currentClass::$humanReadableFormats['dateDifferentYear'];
0 ignored issues
show
Bug introduced by
The property humanReadableFormats does not exist on string.
Loading history...
452
        if ($current->format('Y') === $this->format('Y')) {
453
            $dateFormat = $currentClass::$humanReadableFormats['dateSameYear'];
454
        }
455
        
456
        $parsedTxt->date = $currentClass::$humanReadableI18n['others'];
0 ignored issues
show
Bug introduced by
The property humanReadableI18n does not exist on string.
Loading history...
457
        $parsedTxt->time = $currentClass::$humanReadableI18n['time_part'];
458
        
459
        $date = $this->format($dateFormat);
460
        $time = $this->format($currentClass::$humanReadableFormats['time']);
461
        
462
        $this->humanParseDateAndTimeText($parsedTxt, $date, $time);
463
    }
464
    
465
    /**
466
     * Replace the expression "{date}" by the $date value and the expression
467
     * "{time}" by the $time value into properties "date" and "time" of the
468
     * $parsedTxt object.
469
     * 
470
     * @param object $parsedTxt Texts returned by humanReadable method
471
     * @param string $date The date value used to replace "{date}" into texts
472
     * @param string $time The time value used to replace "{time}" into texts
473
     * 
474
     * @return void
475
     */
476
    protected function humanParseDateAndTimeText(
477
        $parsedTxt,
478
        string $date,
479
        string $time
480
    ) {
481
        $parsedTxt->date = str_replace('{date}', $date, $parsedTxt->date);
482
        $parsedTxt->date = str_replace('{time}', $time, $parsedTxt->date);
483
        
484
        $parsedTxt->time = str_replace('{date}', $date, $parsedTxt->time);
485
        $parsedTxt->time = str_replace('{time}', $time, $parsedTxt->time);
486
    }
487
}
488