root/fTime.php

Revision 775, 11.6 kB (checked in by wbond, 5 months ago)

Fixed ticket #375 - added the $simple parameter to fDate::getFuzzyDifference(), fTime::getFuzzyDifference() and fTimestamp::getFuzzyDifference()

LineHide Line Numbers
1 <?php
2 /**
3  * Represents a time of day as a value object
4  *
5  * @copyright  Copyright (c) 2008-2010 Will Bond
6  * @author     Will Bond [wb] <will@flourishlib.com>
7  * @license    http://flourishlib.com/license
8  *
9  * @package    Flourish
10  * @link       http://flourishlib.com/fTime
11  *
12  * @version    1.0.0b9
13  * @changes    1.0.0b9  Added the `$simple` parameter to ::getFuzzyDifference() [wb, 2010-03-15]
14  * @changes    1.0.0b8  Added a call to fTimestamp::callUnformatCallback() in ::__construct() for localization support [wb, 2009-06-01]
15  * @changes    1.0.0b7  Backwards compatibility break - Removed ::getSecondsDifference(), added ::eq(), ::gt(), ::gte(), ::lt(), ::lte() [wb, 2009-03-05]
16  * @changes    1.0.0b6  Fixed an outdated fCore method call [wb, 2009-02-23]
17  * @changes    1.0.0b5  Updated for new fCore API [wb, 2009-02-16]
18  * @changes    1.0.0b4  Fixed ::__construct() to properly handle the 5.0 to 5.1 change in strtotime() [wb, 2009-01-21]
19  * @changes    1.0.0b3  Added support for CURRENT_TIMESTAMP and CURRENT_TIME SQL keywords [wb, 2009-01-11]
20  * @changes    1.0.0b2  Removed the adjustment amount check from ::adjust() [wb, 2008-12-31]
21  * @changes    1.0.0b   The initial implementation [wb, 2008-02-12]
22  */
23 class fTime
24 {
25     /**
26     * Composes text using fText if loaded
27     *
28     * @param  string  $message    The message to compose
29     * @param  mixed   $component  A string or number to insert into the message
30     * @param  mixed   ...
31     * @return string  The composed and possible translated message
32     */
33     static protected function compose($message)
34     {
35         $args = array_slice(func_get_args(), 1);
36        
37         if (class_exists('fText', FALSE)) {
38             return call_user_func_array(
39                 array('fText', 'compose'),
40                 array($message, $args)
41             );
42         } else {
43             return vsprintf($message, $args);
44         }
45     }
46    
47    
48     /**
49     * A timestamp of the time
50     *
51     * @var integer
52     */
53     private $time;
54    
55    
56     /**
57     * Creates the time to represent, no timezone is allowed since times don't have timezones
58     *
59     * @throws fValidationException  When `$time` is not a valid time
60     *
61     * @param  fTime|object|string|integer $time  The time to represent, `NULL` is interpreted as now
62     * @return fTime
63     */
64     public function __construct($time=NULL)
65     {
66         if ($time === NULL) {
67             $timestamp = time();
68         } elseif (is_numeric($time) && ctype_digit($time)) {
69             $timestamp = (int) $time;
70         } elseif (is_string($time) && in_array(strtoupper($time), array('CURRENT_TIMESTAMP', 'CURRENT_TIME'))) {
71             $timestamp = time();
72         } else {
73             if (is_object($time) && is_callable(array($time, '__toString'))) {
74                 $time = $time->__toString();   
75             } elseif (is_numeric($time) || is_object($time)) {
76                 $time = (string) $time;   
77             }
78            
79             $time = fTimestamp::callUnformatCallback($time);
80            
81             $timestamp = strtotime($time);
82         }
83        
84         $is_51    = fCore::checkVersion('5.1');
85         $is_valid = ($is_51 && $timestamp !== FALSE) || (!$is_51 && $timestamp !== -1);
86        
87         if (!$is_valid) {
88             throw new fValidationException(
89                 'The time specified, %s, does not appear to be a valid time',
90                 $time
91             );
92         }
93        
94         $this->time = strtotime(date('1970-01-01 H:i:s', $timestamp));
95     }
96    
97    
98     /**
99     * All requests that hit this method should be requests for callbacks
100     *
101     * @internal
102      *
103     * @param  string $method  The method to create a callback for
104     * @return callback  The callback for the method requested
105     */
106     public function __get($method)
107     {
108         return array($this, $method);       
109     }
110    
111    
112     /**
113     * Returns this time in `'H:i:s'` format
114     *
115     * @return string  The `'H:i:s'` format of this time
116     */
117     public function __toString()
118     {
119         return date('H:i:s', $this->time);
120     }
121    
122    
123     /**
124     * Changes the time by the adjustment specified, only adjustments of `'hours'`, `'minutes'`, and `'seconds'` are allowed
125     *
126     * @throws fValidationException  When `$adjustment` is not a valid relative time measurement
127     *
128     * @param  string $adjustment  The adjustment to make
129     * @return fTime  The adjusted time
130     */
131     public function adjust($adjustment)
132     {
133         $timestamp = strtotime($adjustment, $this->time);
134        
135         if ($timestamp === FALSE || $timestamp === -1) {
136             throw new fValidationException(
137                 'The adjustment specified, %s, does not appear to be a valid relative time measurement',
138                 $adjustment
139             );
140         }
141        
142         return new fTime($timestamp);
143     }
144    
145    
146     /**
147     * If this time is equal to the time passed
148     *
149     * @param  fTime|object|string|integer $other_time  The time to compare with, `NULL` is interpreted as today
150     * @return boolean  If this time is equal to the one passed
151     */
152     public function eq($other_time=NULL)
153     {
154         $other_time = new fTime($other_time);
155         return $this->time == $other_time->time;
156     }
157    
158    
159     /**
160     * Formats the time
161     *
162     * @throws fValidationException  When a non-time formatting character is included in `$format`
163     *
164     * @param  string $format  The [http://php.net/date date()] function compatible formatting string, or a format name from fTimestamp::defineFormat()
165     * @return string  The formatted time
166     */
167     public function format($format)
168     {
169         $format = fTimestamp::translateFormat($format);
170        
171         $restricted_formats = 'cdDeFIjlLmMnNoOPrStTUwWyYzZ';
172         if (preg_match('#(?!\\\\).[' . $restricted_formats . ']#', $format)) {
173             throw new fProgrammerException(
174                 'The formatting string, %1$s, contains one of the following non-time formatting characters: %2$s',
175                 $format,
176                 join(', ', str_split($restricted_formats))
177             );
178         }
179        
180         return fTimestamp::callFormatCallback(date($format, $this->time));
181     }
182    
183    
184     /**
185     * Returns the approximate difference in time, discarding any unit of measure but the least specific.
186     *
187     * The output will read like:
188     *
189     *  - "This time is `{return value}` the provided one" when a time it passed
190     *  - "This time is `{return value}`" when no time is passed and comparing with the current time
191     *
192     * Examples of output for a time passed might be:
193     *
194     *  - `'5 minutes after'`
195     *  - `'2 hours before'`
196     *  - `'at the same time'`
197     *
198     * Examples of output for no time passed might be:
199     *
200     *  - `'5 minutes ago'`
201     *  - `'2 hours ago'`
202     *  - `'right now'`
203     *
204     * You would never get the following output since it includes more than one unit of time measurement:
205     *
206     *  - `'5 minutes and 28 seconds'`
207     *  - `'1 hour, 15 minutes'`
208     *
209     * Values that are close to the next largest unit of measure will be rounded up:
210     *
211     *  - `'55 minutes'` would be represented as `'1 hour'`, however `'45 minutes'` would not
212     *
213     * @param  fTime|object|string|integer $other_time  The time to create the difference with, `NULL` is interpreted as now
214     * @param  boolean                     $simple      When `TRUE`, the returned value will only include the difference in the two times, but not `from now`, `ago`, `after` or `before`
215     * @param  boolean                     :$simple
216     * @return string  The fuzzy difference in time between the this time and the one provided
217     */
218     public function getFuzzyDifference($other_time=NULL, $simple=FALSE)
219     {
220         if (is_bool($other_time)) {
221             $simple     = $other_time;
222             $other_time = NULL;
223         }
224        
225         $relative_to_now = FALSE;
226         if ($other_time === NULL) {
227             $relative_to_now = TRUE;
228         }
229         $other_time = new fTime($other_time);
230        
231         $diff = $this->time - $other_time->time;
232        
233         if (abs($diff) < 10) {
234             if ($relative_to_now) {
235                 return self::compose('right now');
236             }
237             return self::compose('at the same time');
238         }
239        
240         static $break_points = array();
241         if (!$break_points) {
242             $break_points = array(
243                 /* 45 seconds  */
244                 45     => array(1,     self::compose('second'), self::compose('seconds')),
245                 /* 45 minutes  */
246                 2700   => array(60,    self::compose('minute'), self::compose('minutes')),
247                 /* 18 hours    */
248                 64800  => array(3600self::compose('hour'),   self::compose('hours')),
249                 /* 5 days      */
250                 432000 => array(86400, self::compose('day'),    self::compose('days'))
251             );
252         }
253        
254         foreach ($break_points as $break_point => $unit_info) {
255             if (abs($diff) > $break_point) { continue; }
256            
257             $unit_diff = round(abs($diff)/$unit_info[0]);
258             $units     = fGrammar::inflectOnQuantity($unit_diff, $unit_info[1], $unit_info[2]);
259             break;
260         }
261        
262         if ($simple) {
263             return self::compose('%1$s %2$s', $unit_diff, $units);
264         }
265        
266         if ($relative_to_now) {
267             if ($diff > 0) {
268                 return self::compose('%1$s %2$s from now', $unit_diff, $units);
269             }
270            
271             return self::compose('%1$s %2$s ago', $unit_diff, $units);
272         }
273        
274        
275         if ($diff > 0) {
276             return self::compose('%1$s %2$s after', $unit_diff, $units);
277         }
278        
279         return self::compose('%1$s %2$s before', $unit_diff, $units);
280     }
281    
282    
283     /**
284     * If this time is greater than the time passed
285     *
286     * @param  fTime|object|string|integer $other_time  The time to compare with, `NULL` is interpreted as now
287     * @return boolean  If this time is greater than the one passed
288     */
289     public function gt($other_time=NULL)
290     {
291         $other_time = new fTime($other_time);
292         return $this->time > $other_time->time;
293     }
294    
295    
296     /**
297     * If this time is greater than or equal to the time passed
298     *
299     * @param  fTime|object|string|integer $other_time  The time to compare with, `NULL` is interpreted as now
300     * @return boolean  If this time is greater than or equal to the one passed
301     */
302     public function gte($other_time=NULL)
303     {
304         $other_time = new fTime($other_time);
305         return $this->time >= $other_time->time;
306     }
307    
308    
309     /**
310     * If this time is less than the time passed
311     *
312     * @param  fTime|object|string|integer $other_time  The time to compare with, `NULL` is interpreted as today
313     * @return boolean  If this time is less than the one passed
314     */
315     public function lt($other_time=NULL)
316     {
317         $other_time = new fTime($other_time);
318         return $this->time < $other_time->time;
319     }
320    
321    
322     /**
323     * If this time is less than or equal to the time passed
324     *
325     * @param  fTime|object|string|integer $other_time  The time to compare with, `NULL` is interpreted as today
326     * @return boolean  If this time is less than or equal to the one passed
327     */
328     public function lte($other_time=NULL)
329     {
330         $other_time = new fTime($other_time);
331         return $this->time <= $other_time->time;
332     }
333    
334    
335     /**
336     * Modifies the current time, creating a new fTime object
337     *
338     * The purpose of this method is to allow for easy creation of a time
339     * based on this time. Below are some examples of formats to
340     * modify the current time:
341     *
342     *  - `'17:i:s'` to set the hour of the time to 5 PM
343     *  - 'H:00:00'` to set the time to the beginning of the current hour
344     *
345     * @param  string $format  The current time will be formatted with this string, and the output used to create a new object
346     * @return fTime  The new time
347     */
348     public function modify($format)
349     {
350        return new fTime($this->format($format));
351     }
352 }
353  
354  
355  
356 /**
357  * Copyright (c) 2008-2010 Will Bond <will@flourishlib.com>
358  *
359  * Permission is hereby granted, free of charge, to any person obtaining a copy
360  * of this software and associated documentation files (the "Software"), to deal
361  * in the Software without restriction, including without limitation the rights
362  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
363  * copies of the Software, and to permit persons to whom the Software is
364  * furnished to do so, subject to the following conditions:
365  *
366  * The above copyright notice and this permission notice shall be included in
367  * all copies or substantial portions of the Software.
368  *
369  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
370  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
371  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
372  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
373  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
374  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
375  * THE SOFTWARE.
376  */