ConvertHelper_DurationConverter::convert_day()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
/**
3
 * File containing the {@see AppUtils\ConvertHelper_DurationConverter} class.
4
 *
5
 * @package Application Utils
6
 * @subpackage ConvertHelper
7
 * @see ConvertHelper_DurationConverter
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppUtils;
13
14
use DateTime;
15
16
/**
17
 * Converts a timespan to a human readable duration string,
18
 * e.g. "2 months", "4 minutes".
19
 * 
20
 * @package Application Utils
21
 * @subpackage ConvertHelper
22
 * @author Sebastian Mordziol <[email protected]>
23
 * 
24
 * @link http://www.sajithmr.com/php-time-ago-calculation/
25
 */
26
class ConvertHelper_DurationConverter
27
{
28
    public const ERROR_NO_DATE_FROM_SET = 43401;
29
    
30
    public const SECONDS_PER_MINUTE = 60;
31
    public const SECONDS_PER_HOUR = 3600;
32
    public const SECONDS_PER_DAY = 86400;
33
    public const SECONDS_PER_WEEK = 604800;
34
    public const SECONDS_PER_MONTH_APPROX = 2505600; // imprecise - for 29 days, only for approximations. 
35
    public const SECONDS_PER_YEAR = 31536000;
36
    
37
   /**
38
    * @var int|NULL
39
    */
40
    protected $dateFrom;
41
    
42
   /**
43
    * @var int|NULL
44
    */
45
    protected $dateTo;
46
    
47
   /**
48
    * @var bool
49
    */
50
    protected $future = false;
51
    
52
   /**
53
    * @var string
54
    */
55
    protected $interval = '';
56
    
57
   /**
58
    * @var int
59
    */
60
    protected $difference = 0;
61
    
62
   /**
63
    * @var int
64
    */
65
    protected $dateDiff = 0;
66
    
67
   /**
68
    * @var array<string,array<string,string>>|NULL
69
    */
70
    protected static $texts = null;
71
    
72
    public function __construct()
73
    {
74
        if(class_exists('\AppLocalize\Localization')) {
75
            \AppLocalize\Localization::onLocaleChanged(array($this, 'handle_localeChanged'));
0 ignored issues
show
Bug introduced by
The type AppLocalize\Localization 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...
76
        }
77
    }
78
    
79
    /**
80
     * Called whenever the application locale has changed,
81
     * to reset the internal translation cache.
82
     */
83
    public function handle_localeChanged() : void
84
    {
85
        // force the texts to be refreshed when needed.
86
        self::$texts = null;
87
    }
88
    
89
   /**
90
    * Sets the origin date to calculate from.
91
    * 
92
    * NOTE: if this is further in the future than
93
    * the to: date, it will be considered as a 
94
    * calculation for something to come, i.e. 
95
    * "In two days".
96
    *  
97
    * @param DateTime $date
98
    * @return ConvertHelper_DurationConverter
99
    */
100
    public function setDateFrom(DateTime $date) : ConvertHelper_DurationConverter
101
    {
102
        $this->dateFrom = ConvertHelper::date2timestamp($date);
103
        
104
        return $this;
105
    }
106
    
107
   /**
108
    * Sets the date to calculate to. Defaults to 
109
    * the current time if not set.
110
    * 
111
    * @param DateTime $date
112
    * @return ConvertHelper_DurationConverter
113
    */
114
    public function setDateTo(DateTime $date) : ConvertHelper_DurationConverter
115
    {
116
        $this->dateTo = ConvertHelper::date2timestamp($date);
117
        
118
        return $this;
119
    }
120
    
121
   /**
122
    * Converts the specified dates to a human-readable string.
123
    * 
124
    * @throws ConvertHelper_Exception
125
    * @return string
126
    * 
127
    * @see ConvertHelper_DurationConverter::ERROR_NO_DATE_FROM_SET
128
    */
129
    public function convert() : string
130
    {
131
        $this->initTexts();
132
        $this->resolveCalculations();
133
        
134
        $epoch = 'past';
135
        $key = 'singular';
136
        if($this->dateDiff > 1) {
137
            $key = 'plural';
138
        }
139
        
140
        if($this->future) {
141
            $epoch = 'future'; 
142
        }
143
        
144
        $key .= '-'.$epoch;
145
        
146
        $text = self::$texts[$this->interval][$key];
147
        
148
        return str_replace('$value', (string)$this->dateDiff, $text);
149
    }
150
    
151
    protected function initTexts() : void
152
    {
153
        if(isset(self::$texts)) {
154
            return;
155
        }
156
        
157
        self::$texts = array(
158
            'y' => array(
159
                'singular-future' => t('In one year'),
160
                'plural-future' => t('In %1s years', '$value'),
161
                'singular-past' => t('One year ago'),
162
                'plural-past' => t('%1s years ago', '$value')
163
            ),
164
            'm' => array(
165
                'singular-future' => t('In one month'),
166
                'plural-future' => t('In %1s months', '$value'),
167
                'singular-past' => t('One month ago'),
168
                'plural-past' => t('%1s months ago', '$value')
169
            ),
170
            'ww' => array(
171
                'singular-future' => t('In one week'),
172
                'plural-future' => t('In %1s weeks', '$value'),
173
                'singular-past' => t('One week ago'),
174
                'plural-past' => t('%1s weeks ago', '$value')
175
            ),
176
            'd' => array(
177
                'singular-future' => t('In one day'),
178
                'plural-future' => t('In %1s days', '$value'),
179
                'singular-past' => t('One day ago'),
180
                'plural-past' => t('%1s days ago', '$value')
181
            ),
182
            'h' => array(
183
                'singular-future' => t('In one hour'),
184
                'plural-future' => t('In %1s hours', '$value'),
185
                'singular-past' => t('One hour ago'),
186
                'plural-past' => t('%1s hours ago', '$value')
187
            ),
188
            'n' => array(
189
                'singular-future' => t('In one minute'),
190
                'plural-future' => t('In %1s minutes', '$value'),
191
                'singular-past' => t('One minute ago'),
192
                'plural-past' => t('%1s minutes ago', '$value')
193
            ),
194
            's' => array(
195
                'singular-future' => t('In one second'),
196
                'plural-future' => t('In %1s seconds', '$value'),
197
                'singular-past' => t('One second ago'),
198
                'plural-past' => t('%1s seconds ago', '$value')
199
            )
200
        );
201
    }
202
    
203
    protected function convert_minute() : int
204
    {
205
        return (int)floor($this->difference / self::SECONDS_PER_MINUTE);
206
    }
207
    
208
    protected function convert_hour() : int
209
    {
210
        return (int)floor($this->difference / self::SECONDS_PER_HOUR);
211
    }
212
    
213
    protected function convert_week() : int
214
    {
215
        return (int)floor($this->difference / self::SECONDS_PER_WEEK);
216
    }
217
    
218
    protected function convert_day() : int
219
    {
220
        return (int)floor($this->difference / self::SECONDS_PER_DAY);
221
    }
222
    
223
    protected function convert_year() : int
224
    {
225
        return (int)floor($this->difference / self::SECONDS_PER_YEAR);
226
    }
227
    
228
    protected function convert_month() : int
229
    {
230
        $months_difference = (int)floor($this->difference / self::SECONDS_PER_MONTH_APPROX);
231
        
232
        $hour = (int)date("H", $this->dateFrom);
233
        $min = (int)date("i", $this->dateFrom);
234
        $sec = (int)date("s", $this->dateFrom);
235
        $month = (int)date("n", $this->dateFrom);
236
        $day = (int)date("j", $this->dateTo);
237
        $year = (int)date("Y", $this->dateFrom);
238
        
239
        while(mktime($hour, $min, $sec, $month + ($months_difference), $day, $year) < $this->dateTo)
240
        {
241
            $months_difference++;
242
        }
243
        
244
        $datediff = $months_difference;
245
        
246
        // We need this in here because it is possible
247
        // to have an 'm' interval and a months
248
        // difference of 12 because we are using 29 days
249
        // in a month
250
        if ($datediff == 12) {
251
            $datediff--;
252
        }
253
        
254
        return $datediff;
255
    }
256
    
257
    protected function resolveCalculations() : void
258
    {
259
        if(!isset($this->dateFrom))
260
        {
261
            throw new ConvertHelper_Exception(
262
                'No date from has been specified.',
263
                null,
264
                self::ERROR_NO_DATE_FROM_SET
265
            );
266
        }
267
        
268
        // no date to set? Assume we want to use today.
269
        if(!isset($this->dateTo))
270
        {
271
            $this->dateTo = time();
272
        }
273
        
274
        $this->difference = $this->resolveDifference();
275
        $this->interval = $this->resolveInterval();
276
        $this->dateDiff = $this->resolveDateDiff();
277
    }
278
    
279
    protected function resolveInterval() : string
280
    {
281
        // If difference is less than 60 seconds,
282
        // seconds is a good interval of choice
283
        if ($this->difference < self::SECONDS_PER_MINUTE) 
284
        {
285
            return "s";
286
        }
287
        
288
        // If difference is between 60 seconds and
289
        // 60 minutes, minutes is a good interval
290
        if ($this->difference < self::SECONDS_PER_HOUR) 
291
        {
292
            return "n";
293
        }
294
        
295
        // If difference is between 1 hour and 24 hours
296
        // hours is a good interval
297
        if ($this->difference < self::SECONDS_PER_DAY) 
298
        {
299
            return "h";
300
        }
301
        
302
        // If difference is between 1 day and 7 days
303
        // days is a good interval
304
        if ($this->difference < self::SECONDS_PER_WEEK) 
305
        {
306
            return "d";
307
        }
308
        
309
        // If difference is between 1 week and 30 days
310
        // weeks is a good interval
311
        if ($this->difference < self::SECONDS_PER_MONTH_APPROX) 
312
        {
313
            return "ww";
314
        }
315
        
316
        // If difference is between 30 days and 365 days
317
        // months is a good interval, again, the same thing
318
        // applies, if the 29th February happens to exist
319
        // between your 2 dates, the function will return
320
        // the 'incorrect' value for a day
321
        if ($this->difference < self::SECONDS_PER_YEAR) 
322
        {
323
            return "m";
324
        }
325
        
326
        return "y";
327
    }
328
    
329
    protected function resolveDifference() : int
330
    {
331
        // Calculate the difference in seconds betweeen
332
        // the two timestamps
333
        
334
        $difference = $this->dateTo - $this->dateFrom;
335
        
336
        if($difference < 0)
337
        {
338
            $difference = $difference * -1;
339
            $this->future = true;
340
        }
341
        
342
        return $difference;
343
    }
344
    
345
    protected function resolveDateDiff() : int
346
    {
347
        // Based on the interval, determine the
348
        // number of units between the two dates
349
        // From this point on, you would be hard
350
        // pushed telling the difference between
351
        // this function and DateDiff. If the $datediff
352
        // returned is 1, be sure to return the singular
353
        // of the unit, e.g. 'day' rather 'days'
354
        switch ($this->interval)
355
        {
356
            case "m":
357
                return $this->convert_month();
358
                
359
            case "y":
360
                return $this->convert_year();
361
                
362
            case "d":
363
                return $this->convert_day();
364
                
365
            case "ww":
366
                return $this->convert_week();
367
                
368
            case "h":
369
                return $this->convert_hour();
370
                
371
            case "n":
372
                return $this->convert_minute();
373
        }
374
        
375
        // seconds
376
        return $this->difference;
377
    }
378
379
    /**
380
     * Converts a timestamp into an easily understandable
381
     * format, e.g. "2 hours", "1 day", "3 months"
382
     *
383
     * If you set the date to parameter, the difference
384
     * will be calculated between the two dates and not
385
     * the current time.
386
     *
387
     * @param integer|DateTime $datefrom
388
     * @param integer|DateTime $dateto
389
     * @return string
390
     *
391
     * @throws ConvertHelper_Exception
392
     * @see ConvertHelper_DurationConverter::ERROR_NO_DATE_FROM_SET
393
     */
394
    public static function toString($datefrom, $dateto = -1) : string
395
    {
396
        $converter = new ConvertHelper_DurationConverter();
397
398
        if($datefrom instanceof DateTime)
399
        {
400
            $converter->setDateFrom($datefrom);
401
        }
402
        else
403
        {
404
            $converter->setDateFrom(ConvertHelper_Date::fromTimestamp($datefrom));
405
        }
406
407
        if($dateto instanceof DateTime)
408
        {
409
            $converter->setDateTo($dateto);
410
        }
411
        else if($dateto > 0)
412
        {
413
            $converter->setDateTo(ConvertHelper_Date::fromTimestamp($dateto));
414
        }
415
416
        return $converter->convert();
417
    }
418
}
419