Location: PHPKode > scripts > Time_When > Time_When.php
<?php
/**
 * @package Time
 * @copyright Copyright (c) 2009, Martin Aarhof
 * @author Martin Aarhof <martin at aarhof dot eu>
 * @version 0.1
 */

/**
 * A class to get those nice "just a moment ago", "1 day ago" or "6 hours ago", instead of a simple timestamp.
 * This can also be reconfigured for own use.
 *
 * @todo Better way to count time diff.
 * - Maybe fx. it should use fx. "2 month ago" instead of "1 month ago" when its "1 month + 28 days or so" ?
 * - GET RID OF THAT SWITCH {@link getUnit()}
 * - Implement so endtime can be higher than starttime (+ days)
 *
 * @package Time_When
 *
 */
class Time_When
{

	const EQUALS = '==';
	const LESS_THAN = '<';
	const HIGHER_THAN = '>';

	/**
	 * Placeholder for our types, can be added at {@link addType()} and removed by {@link removeType()}
	 * @var array
	 */
	protected $types = array();

	/**
	 * Placeholder for our timers can be added at {@link addTimer()}
	 * @var array
	 */
	protected $timer = array();

	/**
	 * Identifier to text in type
	 * @var string
	 */
	public $textAssigner = '?';

	/**
	 * Text to replace with the time from the type
	 * @var string
	 */
	public $textReplacer = '[NUM]';

	/**
	 * Constructor, in this you can set some default types.
	 * Initiate using {@link getIntance()}
	 *
	 * @return void
	 */

        /**
         * Instance
         * @var Time_When
         */
        static $_instance;

	protected function __construct()
	{

		$this->types = array(
				/* Commented out, until +days works!
				'+sec' => array('time' => 60, 'text' => 'almost right away'),
				'+min' => array('time' => (60*60), 'text' => '? ?', 'assign' => array('[NUM]', array(self::LESS_THAN, 2, 'minute', 'minutes'))),
				'+hour' => array('time' => (60*60*60), 'text' => '? ?', 'assign' => array('[NUM]', array(self::EQUALS, 1, 'hour', 'hours'))),
				'+day' => array('time' => (60*60*60*24), 'text' => '? ?', 'assign' => array('[NUM]', array(self::HIGHER_THAN, 1, 'days', 'day'))),
				'+month' => array('time' => (60*60*60*24*30), 'text' => '? ?', 'assign' => array('[NUM]', array(self::EQUALS, 1, 'month', 'months'))),
				'+year' => array('time' => (60*60*60*24*30*12), 'text' => '? ?', 'assign' => array('[NUM]', array(self::EQUALS, 1, 'year', 'years'))),
				*/
				'now' => array('time' => 0, 'text' => 'Just now!'),
				'-sec' => array('time' => -60, 'text' => 'moments ago'),
				'-min' => array('time' => -(60*60), 'text' => $this->textAssigner . ' ' . $this->textAssigner . ' ago', 'assign' => array($this->textReplacer, array(self::LESS_THAN, 2, 'minute', 'minutes'))),
				'-hour' => array('time' => -(60*60*60), 'text' => $this->textAssigner . ' ' . $this->textAssigner . ' ago', 'assign' => array($this->textReplacer, array(self::EQUALS, 1, 'hour', 'hours'))),
				'-day' => array('time' => -(60*60*60*24), 'text' => $this->textAssigner . ' ' . $this->textAssigner . ' ago', 'assign' => array($this->textReplacer, array(self::HIGHER_THAN, 1, 'days', 'day'))),
				'-month' => array('time' => -(60*60*60*24*30), 'text' => $this->textAssigner . ' ' . $this->textAssigner . ' ago', 'assign' => array($this->textReplacer, array(self::EQUALS, 1, 'month', 'months'))),
				'-year' => array('time' => -(60*60*60*24*30*12), 'text' => $this->textAssigner . ' ' . $this->textAssigner . ' ago', 'assign' => array($this->textReplacer, array(self::EQUALS, 1, 'year', 'years'))),
				'-galaxy' => array('time' => -(60*60*60*24*30*12*200), 'text' => 'Long, long time ago, in a far far galaxy'),
			);

                $this->timer = array();

	}

	/**
	 * Disallow cloning - this is a singleton!
	 *
	 */
	private function __clone()
	{
	}

	/**
	 * Singleton instance
	 *
	 * @return Time_When
	 */
	public static function getInstance()
	{
		if (null === self::$_instance) {
			self::$_instance = new self();
		}

		return self::$_instance;
	}

        /**
         * Resetting everything
         *
         * @return self
         */
        public function reset()
        {
            $this->__construct();
            return $this;
        }

	/**
	 * Method to add new types {@link types} to our conversion.
	 * $name HAS TO BE UNIQUE!
	 *
	 * @example example.php 0 38
	 * @param string $name a unique identifier
	 * @param array $options - $options['time'] and $options['text'] is required! - see example for options
	 * @return self
	 */
	public function addType($name, array $options)
	{
		if (!isset($options['time']) || !isset($options['text'])) {
			trigger_error('addType requires $options["time"] and $options["text"]', E_USER_ERROR);
		}

		if (isset($this->types[$name])) {
			trigger_error('$this->types already have a ' . $name . ', please use a unique identifier', E_USER_ERROR);
		}

		$this->types[$name] = $options;
		return $this;
	}

	/**
	 * Method to remove a type from {@link types} using the name
	 * @example example.php 0 38
	 * @param string $name
	 * @return bool
	 */
	public function removeType($name)
	{
		if (isset($this->types[$name])) {
			unset($this->types[$name]);
			return true;
		}

		return false;
	}

	/**
	 * Method to add timers to the conversion
	 * @example example.php 38 111
	 * @param timestamp $endtime timestamp of the endtime
	 * @param timestamp $starttime timestamp of the starttime ( null = time() )
	 * @param mixed $id a unique identifier if you convert many at one time
	 * @return self
	 */
	public function addTimer($endtime, $starttime = null, $id = null)
	{
		if ($endtime > ($starttime === null ? time() : $starttime)) {
			trigger_error('endtime can not be higher than startime - NOT IMPLEMENTED YET', E_USER_ERROR);
		}

		// How long time is there between our dates in seconds?
		$array = array('starttimer' => ($starttime === null ? time() : $starttime), 'endtimer' => $endtime, 'timer' => $endtime - ($starttime === null ? time() : $starttime));
		if ($id !== null) $this->timer[$id] = $array;
		else $this->timer[] = $array;

		return $this;
	}

	/**
	 * Method to remove one or all timers
	 * @example example.php 111
	 * @param string $id a unique identifier if you want to remove only one
	 * @return int numbers of deleted timers
	 */
	public function removeTimer($id = null)
	{
		if ($id === null) {
			$count = count($this->timer);
			$this->timer = array();
			return $count;
		}

		if (isset($this->timer[$id])) {
			unset($this->timer[$id]);
			return 1;
		} else {
			return 0;
		}

	}

	/**
	 * Method which returns a array of needles found in haystack
	 * @param string $haystack
	 * @param string $needle
	 * @param integer $offset
	 * @return array
	 */
	protected static function strallpos($haystack,$needle,$offset = 0)
	{
		$result = array();
		for($i = $offset; $i<strlen($haystack); $i++){
			$pos = strpos($haystack,$needle,$i);
			if($pos !== FALSE){
				$offset =  $pos;
				if($offset >= $i){
					$i = $offset;
					$result[] = $offset;
				}
			}
		}
		return $result;
	}

	/**
	 * Our main method which convert our {@link timer} using {@link types} to get our conversion
	 * @param string $timerId
	 * @param string $name
	 * @return array
	 * @access protected
	 */
	protected function getUnit($timerId, $name)
	{
		$options = $this->types[$name];
		$timer = $this->timer[$timerId];

		$timestring = ($timer['timer'] == 0 ? 0 : $timer['timer']/$options['time']);

		$assignments = (isset($options['assign']) ? count($options['assign']) : 0);
		$questionmarks = count(self::strallpos($options['text'], $this->textAssigner));
		if ($questionmarks > $assignments) {
			trigger_error('There are more ' . $this->textAssigner . ' (' . $questionmarks . ') in the text than in the assignments (' . $assignments. ')', E_USER_NOTICE);
		}
		if ($questionmarks < $assignments) {
			trigger_error('There are less ' . $this->textAssigner . ' (' . $questionmarks . ') in the text than in the assignments (' . $assignments. ')', E_USER_NOTICE);
		}

		if ($assignments) {
			$assignments = array();
			foreach ($options['assign'] AS $assign) {

				if (is_array($assign)) {
					list($sep, $num, $true, $false) = $assign;

					/**
					 * @todo get rid of the switch!
					 * $assignments[] = ($timer $sep $num ? $true : $false);
					 * would be the best!
					 */
					switch ($sep)
					{
						case self::LESS_THAN:
							$assignments[] = (floor($timestring) < (int)$num ? $true : $false);
							break;
						case self::HIGHER_THAN:
							$assignments[] = (floor($timestring) > (int)$num ? $true : $false);
							break;
						case self::EQUALS:
							$assignments[] = (floor($timestring) == (int)$num ? $true : $false);
							break;
					}

				} else {
					$assignments[] = str_replace($this->textReplacer, floor($timestring), $assign);
				}
			}

			$options['timestring'] = array('floored' => floor($timestring), 'normal' => $timestring);
			$options['assignments'] = $assignments;
			$options['text'] = ($this->textAssigner != '%s' ? str_replace($this->textAssigner, '%s', $options['text']) : $options['text']);
			$text = call_user_func_array('sprintf', array_merge((array)$options['text'], $assignments));

		} else {
			$text = $options['text'];
		}

		return array('converter' => array($name => $options), 'translated' => $text);

	}

	/**
	 * Just a method to sort our array
	 * @param array $a
	 * @param array $b
	 * @return int
	 */
	static protected function sorttypes($a, $b)
	{

		if ($a['time'] == $b['time']) return 0;
		return ($a['time'] > $b['time']) ? -1 : 1;

	}

	/**
	 * To get our results, and only get the results.
	 * If more than one {@link timer} this will return array otherwise just a string
	 * @example example.php 38 111
	 * @return string/array
	 */
	public function __toString()
	{

		$results = $this->getResult();
		$returns = array();
		$count = count($this->timer);
		foreach($this->timer AS $id => $timer) {
			if ($count == 1) return $timer['result']['translated'];
			else $returns[$id] = $timer['result']['translated'];
		}

		$this->removeTimer();
		return $returns;

	}

	/**
	 * @link __toString()}
	 * @return string/array
	 */
	public function toString()
	{
		return $this->__toString();
	}

	/**
	 * To get our results, with all information about the conversion tool used.
	 * @example example.php 38 111
	 * @return array
	 */
	public function getResult()
	{

		// Lets start by sorting the types array
		uasort($this->types, array(self, 'sorttypes'));

		// Which should we use?
		foreach ($this->timer AS $timerid => $timer) {
			$last = null;
			$firstrun = true;
			foreach ($this->types AS $name => $options) {
				if ($timer['timer'] < 0 && $options['time'] > 0) continue;

				if ($firstrun === true && ($options['time'] <= $timer['timer'])) {
					$this->timer[$timerid]['result'] = $this->getUnit($timerid, $name);
					break;
				}

                                // If the time in the types are bigger than the timer, we dont want that,
				// but we will store the name in the $last
				if ($options['time'] >= $timer['timer']) {
					$firstrun = false;
					$last = $name;
					continue;
				}

				// Aha, here the timer is larger than types timer, so we will pick the last one found
				// But if we havent found a $last, we will get the name from the loop
				$this->timer[$timerid]['result'] = $this->getUnit($timerid, ($last === null ? $name : $last) );

			}

			if (! $this->timer[$timerid]['result']) {
				// Here the timer is larger than all the types timer, so we will use the last found
				$this->timer[$timerid]['result'] = $this->getUnit($timerid, $last);
			}
		}

		return $this->timer;

	}

}
Return current item: Time_When