1<?php defined('SYSPATH') OR die('No direct access allowed.');
2/**
3 * Date helper class.
4 *
5 * $Id: date.php 4316 2009-05-04 01:03:54Z kiall $
6 *
7 * @package    Core
8 * @author     Kohana Team
9 * @copyright  (c) 2007-2008 Kohana Team
10 * @license    http://kohanaphp.com/license.html
11 */
12class date_Core {
13
14	/**
15	 * Converts a UNIX timestamp to DOS format.
16	 *
17	 * @param   integer  UNIX timestamp
18	 * @return  integer
19	 */
20	public static function unix2dos($timestamp = FALSE)
21	{
22		$timestamp = ($timestamp === FALSE) ? getdate() : getdate($timestamp);
23
24		if ($timestamp['year'] < 1980)
25		{
26			return (1 << 21 | 1 << 16);
27		}
28
29		$timestamp['year'] -= 1980;
30
31		// What voodoo is this? I have no idea... Geert can explain it though,
32		// and that's good enough for me.
33		return ($timestamp['year']    << 25 | $timestamp['mon']     << 21 |
34		        $timestamp['mday']    << 16 | $timestamp['hours']   << 11 |
35		        $timestamp['minutes'] << 5  | $timestamp['seconds'] >> 1);
36	}
37
38	/**
39	 * Converts a DOS timestamp to UNIX format.
40	 *
41	 * @param   integer  DOS timestamp
42	 * @return  integer
43	 */
44	public static function dos2unix($timestamp = FALSE)
45	{
46		$sec  = 2 * ($timestamp & 0x1f);
47		$min  = ($timestamp >>  5) & 0x3f;
48		$hrs  = ($timestamp >> 11) & 0x1f;
49		$day  = ($timestamp >> 16) & 0x1f;
50		$mon  = ($timestamp >> 21) & 0x0f;
51		$year = ($timestamp >> 25) & 0x7f;
52
53		return mktime($hrs, $min, $sec, $mon, $day, $year + 1980);
54	}
55
56	/**
57	 * Returns the offset (in seconds) between two time zones.
58	 * @see     http://php.net/timezones
59	 *
60	 * @param   string          timezone that to find the offset of
61	 * @param   string|boolean  timezone used as the baseline
62	 * @return  integer
63	 */
64	public static function offset($remote, $local = TRUE)
65	{
66		static $offsets;
67
68		// Default values
69		$remote = (string) $remote;
70		$local  = ($local === TRUE) ? date_default_timezone_get() : (string) $local;
71
72		// Cache key name
73		$cache = $remote.$local;
74
75		if (empty($offsets[$cache]))
76		{
77			// Create timezone objects
78			$remote = new DateTimeZone($remote);
79			$local  = new DateTimeZone($local);
80
81			// Create date objects from timezones
82			$time_there = new DateTime('now', $remote);
83			$time_here  = new DateTime('now', $local);
84
85			// Find the offset
86			$offsets[$cache] = $remote->getOffset($time_there) - $local->getOffset($time_here);
87		}
88
89		return $offsets[$cache];
90	}
91
92	/**
93	 * Number of seconds in a minute, incrementing by a step.
94	 *
95	 * @param   integer  amount to increment each step by, 1 to 30
96	 * @param   integer  start value
97	 * @param   integer  end value
98	 * @return  array    A mirrored (foo => foo) array from 1-60.
99	 */
100	public static function seconds($step = 1, $start = 0, $end = 60)
101	{
102		// Always integer
103		$step = (int) $step;
104
105		$seconds = array();
106
107		for ($i = $start; $i < $end; $i += $step)
108		{
109			$seconds[$i] = ($i < 10) ? '0'.$i : $i;
110		}
111
112		return $seconds;
113	}
114
115	/**
116	 * Number of minutes in an hour, incrementing by a step.
117	 *
118	 * @param   integer  amount to increment each step by, 1 to 30
119	 * @return  array    A mirrored (foo => foo) array from 1-60.
120	 */
121	public static function minutes($step = 5)
122	{
123		// Because there are the same number of minutes as seconds in this set,
124		// we choose to re-use seconds(), rather than creating an entirely new
125		// function. Shhhh, it's cheating! ;) There are several more of these
126		// in the following methods.
127		return date::seconds($step);
128	}
129
130	/**
131	 * Number of hours in a day.
132	 *
133	 * @param   integer  amount to increment each step by
134	 * @param   boolean  use 24-hour time
135	 * @param   integer  the hour to start at
136	 * @return  array    A mirrored (foo => foo) array from start-12 or start-23.
137	 */
138	public static function hours($step = 1, $long = FALSE, $start = NULL)
139	{
140		// Default values
141		$step = (int) $step;
142		$long = (bool) $long;
143		$hours = array();
144
145		// Set the default start if none was specified.
146		if ($start === NULL)
147		{
148			$start = ($long === FALSE) ? 1 : 0;
149		}
150
151		$hours = array();
152
153		// 24-hour time has 24 hours, instead of 12
154		$size = ($long === TRUE) ? 23 : 12;
155
156		for ($i = $start; $i <= $size; $i += $step)
157		{
158			$hours[$i] = $i;
159		}
160
161		return $hours;
162	}
163
164	/**
165	 * Returns AM or PM, based on a given hour.
166	 *
167	 * @param   integer  number of the hour
168	 * @return  string
169	 */
170	public static function ampm($hour)
171	{
172		// Always integer
173		$hour = (int) $hour;
174
175		return ($hour > 11) ? 'PM' : 'AM';
176	}
177
178	/**
179	 * Adjusts a non-24-hour number into a 24-hour number.
180	 *
181	 * @param   integer  hour to adjust
182	 * @param   string   AM or PM
183	 * @return  string
184	 */
185	public static function adjust($hour, $ampm)
186	{
187		$hour = (int) $hour;
188		$ampm = strtolower($ampm);
189
190		switch ($ampm)
191		{
192			case 'am':
193				if ($hour == 12)
194					$hour = 0;
195			break;
196			case 'pm':
197				if ($hour < 12)
198					$hour += 12;
199			break;
200		}
201
202		return sprintf('%02s', $hour);
203	}
204
205	/**
206	 * Number of days in month.
207	 *
208	 * @param   integer  number of month
209	 * @param   integer  number of year to check month, defaults to the current year
210	 * @return  array    A mirrored (foo => foo) array of the days.
211	 */
212	public static function days($month, $year = FALSE)
213	{
214		static $months;
215
216		// Always integers
217		$month = (int) $month;
218		$year  = (int) $year;
219
220		// Use the current year by default
221		$year  = ($year == FALSE) ? date('Y') : $year;
222
223		// We use caching for months, because time functions are used
224		if (empty($months[$year][$month]))
225		{
226			$months[$year][$month] = array();
227
228			// Use date to find the number of days in the given month
229			$total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1;
230
231			for ($i = 1; $i < $total; $i++)
232			{
233				$months[$year][$month][$i] = $i;
234			}
235		}
236
237		return $months[$year][$month];
238	}
239
240	/**
241	 * Number of months in a year
242	 *
243	 * @return  array  A mirrored (foo => foo) array from 1-12.
244	 */
245	public static function months()
246	{
247		return date::hours();
248	}
249
250	/**
251	 * Returns an array of years between a starting and ending year.
252	 * Uses the current year +/- 5 as the max/min.
253	 *
254	 * @param   integer  starting year
255	 * @param   integer  ending year
256	 * @return  array
257	 */
258	public static function years($start = FALSE, $end = FALSE)
259	{
260		// Default values
261		$start = ($start === FALSE) ? date('Y') - 5 : (int) $start;
262		$end   = ($end   === FALSE) ? date('Y') + 5 : (int) $end;
263
264		$years = array();
265
266		// Add one, so that "less than" works
267		$end += 1;
268
269		for ($i = $start; $i < $end; $i++)
270		{
271			$years[$i] = $i;
272		}
273
274		return $years;
275	}
276
277	/**
278	 * Returns time difference between two timestamps, in human readable format.
279	 *
280	 * @param   integer       timestamp
281	 * @param   integer       timestamp, defaults to the current time
282	 * @param   string        formatting string
283	 * @return  string|array
284	 */
285	public static function timespan($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
286	{
287		// Array with the output formats
288		$output = preg_split('/[^a-z]+/', strtolower((string) $output));
289
290		// Invalid output
291		if (empty($output))
292			return FALSE;
293
294		// Make the output values into keys
295		extract(array_flip($output), EXTR_SKIP);
296
297		// Default values
298		$time1  = max(0, (int) $time1);
299		$time2  = empty($time2) ? time() : max(0, (int) $time2);
300
301		// Calculate timespan (seconds)
302		$timespan = abs($time1 - $time2);
303
304		// All values found using Google Calculator.
305		// Years and months do not match the formula exactly, due to leap years.
306
307		// Years ago, 60 * 60 * 24 * 365
308		isset($years) and $timespan -= 31556926 * ($years = (int) floor($timespan / 31556926));
309
310		// Months ago, 60 * 60 * 24 * 30
311		isset($months) and $timespan -= 2629744 * ($months = (int) floor($timespan / 2629743.83));
312
313		// Weeks ago, 60 * 60 * 24 * 7
314		isset($weeks) and $timespan -= 604800 * ($weeks = (int) floor($timespan / 604800));
315
316		// Days ago, 60 * 60 * 24
317		isset($days) and $timespan -= 86400 * ($days = (int) floor($timespan / 86400));
318
319		// Hours ago, 60 * 60
320		isset($hours) and $timespan -= 3600 * ($hours = (int) floor($timespan / 3600));
321
322		// Minutes ago, 60
323		isset($minutes) and $timespan -= 60 * ($minutes = (int) floor($timespan / 60));
324
325		// Seconds ago, 1
326		isset($seconds) and $seconds = $timespan;
327
328		// Remove the variables that cannot be accessed
329		unset($timespan, $time1, $time2);
330
331		// Deny access to these variables
332		$deny = array_flip(array('deny', 'key', 'difference', 'output'));
333
334		// Return the difference
335		$difference = array();
336		foreach ($output as $key)
337		{
338			if (isset($$key) AND ! isset($deny[$key]))
339			{
340				// Add requested key to the output
341				$difference[$key] = $$key;
342			}
343		}
344
345		// Invalid output formats string
346		if (empty($difference))
347			return FALSE;
348
349		// If only one output format was asked, don't put it in an array
350		if (count($difference) === 1)
351			return current($difference);
352
353		// Return array
354		return $difference;
355	}
356
357	/**
358	 * Returns time difference between two timestamps, in the format:
359	 * N year, N months, N weeks, N days, N hours, N minutes, and N seconds ago
360	 *
361	 * @param   integer       timestamp
362	 * @param   integer       timestamp, defaults to the current time
363	 * @param   string        formatting string
364	 * @return  string
365	 */
366	public static function timespan_string($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
367	{
368		if ($difference = date::timespan($time1, $time2, $output) AND is_array($difference))
369		{
370			// Determine the key of the last item in the array
371			$last = end($difference);
372			$last = key($difference);
373
374			$span = array();
375			foreach ($difference as $name => $amount)
376			{
377				if ($amount === 0)
378				{
379					// Skip empty amounts
380					continue;
381				}
382
383				// Add the amount to the span
384				$span[] = ($name === $last ? ' and ' : ', ').$amount.' '.($amount === 1 ? inflector::singular($name) : $name);
385			}
386
387			// If the difference is less than 60 seconds, remove the preceding and.
388			if (count($span) === 1)
389			{
390				$span[0] = ltrim($span[0], 'and ');
391			}
392
393			// Replace difference by making the span into a string
394			$difference = trim(implode('', $span), ',');
395		}
396		elseif (is_int($difference))
397		{
398			// Single-value return
399			$difference = $difference.' '.($difference === 1 ? inflector::singular($output) : $output);
400		}
401
402		return $difference;
403	}
404
405} // End date