Passed
Push — master ( b691b7...9fda08 )
by Sebastian
02:50
created

ConvertHelper_DurationConverter::resolveDateDiff()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 32
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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