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