<?php
/**
* @package Time
* @copyright Copyright (c) 2009, Martin Aarhof
* @version 1.0
*/
/**
* 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 + 30 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.
*/
public 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'),
);
}
/**
* 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 the time in the types are bigger than the timer, we dont want that,
// but we will store the name in the $last
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 ($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;
}
}
|