timeago.inc.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. // To allow overriding this function as will, if someone wants to use a derived class of `TimeAgo`
  3. if (!function_exists('timeAgoInWords')) {
  4. function timeAgoInWords($timestring, $timezone = null, $language = 'en')
  5. {
  6. $timeAgo = new TimeAgo($timezone, $language);
  7. return $timeAgo->inWords($timestring, "now");
  8. }
  9. }
  10. /**
  11. * This class can help you find out just how much time has passed between
  12. * two dates.
  13. *
  14. * It has two functions you can call:
  15. * inWords() which gives you the "time ago in words" between two dates.
  16. * dateDifference() which returns an array of years,months,days,hours,minutes and
  17. * seconds between the two dates.
  18. *
  19. * @author jimmiw
  20. * @since 0.2.0 (2010/05/05)
  21. * @site http://github.com/jimmiw/php-time-ago
  22. */
  23. class TimeAgo
  24. {
  25. // defines the number of seconds per "unit"
  26. private $secondsPerMinute = 60;
  27. private $secondsPerHour = 3600;
  28. private $secondsPerDay = 86400;
  29. private $secondsPerMonth = 2592000;
  30. private $secondsPerYear = 31536000; // 31622400 seconds on leap years though...
  31. private $timezone;
  32. private $previousTimezone;
  33. // translations variables
  34. private static $language;
  35. private static $timeAgoStrings = null;
  36. /**
  37. * TimeAgo constructor.
  38. * @param null|DateTimeZone $timezone the timezone to use (uses system if none is given)
  39. * @param string $language the language to use (defaults to 'en' for english)
  40. */
  41. public function __construct($timezone = null, $language = 'en')
  42. {
  43. // loads the translation files
  44. self::_loadTranslations($language);
  45. // storing the current timezone
  46. $this->timezone = $timezone;
  47. }
  48. /**
  49. * Fetches the different between $past and $now in a spoken format.
  50. * NOTE: both past and now should be parseable by strtotime
  51. * @param string $past the past date to use
  52. * @param string $now the current time, defaults to now (can be an other time though)
  53. * @return string the difference in spoken format, e.g. 1 day ago
  54. */
  55. public function inWords($past, $now = "now")
  56. {
  57. // sets the default timezone
  58. $this->changeTimezone();
  59. // finds the past in datetime
  60. $past = strtotime($past);
  61. // finds the current datetime
  62. $now = strtotime($now);
  63. // creates the "time ago" string. This always starts with an "about..."
  64. $timeAgo = "";
  65. // finds the time difference
  66. $timeDifference = $now - $past;
  67. // rule 0
  68. // $past is null or empty or ''
  69. if ($past === '' || is_null($past) || empty($past)) {
  70. $timeAgo = $this->_translate('never');
  71. }
  72. // rule 1
  73. // less than 29secs
  74. else if ($timeDifference <= 29) {
  75. $timeAgo = $this->_translate('lessThanAMinute');
  76. }
  77. // rule 2
  78. // more than 29secs and less than 1min29secss
  79. else if ($timeDifference >= 30 && $timeDifference <= 89) {
  80. $timeAgo = $this->_translate('oneMinute');
  81. }
  82. // rule 3
  83. // between 1min30secs and 44mins29secs
  84. else if ($timeDifference >= 90 &&
  85. $timeDifference <= (($this->secondsPerMinute * 44) + 29)
  86. ) {
  87. $minutes = round($timeDifference / $this->secondsPerMinute);
  88. $timeAgo = $this->_translate('lessThanOneHour', $minutes);
  89. }
  90. // rule 4
  91. // between 44mins30secs and 1hour29mins59secs
  92. else if (
  93. $timeDifference >= (($this->secondsPerMinute * 44) + 30)
  94. &&
  95. $timeDifference <= ($this->secondsPerHour + ($this->secondsPerMinute * 29) + 59)
  96. ) {
  97. $timeAgo = $this->_translate('aboutOneHour');
  98. }
  99. // rule 5
  100. // between 1hour29mins59secs and 23hours59mins29secs
  101. else if (
  102. $timeDifference >= (
  103. $this->secondsPerHour +
  104. ($this->secondsPerMinute * 30)
  105. )
  106. &&
  107. $timeDifference <= (
  108. ($this->secondsPerHour * 23) +
  109. ($this->secondsPerMinute * 59) +
  110. 29
  111. )
  112. ) {
  113. $hours = round($timeDifference / $this->secondsPerHour);
  114. $timeAgo = $this->_translate('hours', $hours);
  115. }
  116. // rule 6
  117. // between 23hours59mins30secs and 47hours59mins29secs
  118. else if (
  119. $timeDifference >= (
  120. ($this->secondsPerHour * 23) +
  121. ($this->secondsPerMinute * 59) +
  122. 30
  123. )
  124. &&
  125. $timeDifference <= (
  126. ($this->secondsPerHour * 47) +
  127. ($this->secondsPerMinute * 59) +
  128. 29
  129. )
  130. ) {
  131. $timeAgo = $this->_translate('aboutOneDay');
  132. }
  133. // rule 7
  134. // between 47hours59mins30secs and 29days23hours59mins29secs
  135. else if (
  136. $timeDifference >= (
  137. ($this->secondsPerHour * 47) +
  138. ($this->secondsPerMinute * 59) +
  139. 30
  140. )
  141. &&
  142. $timeDifference <= (
  143. ($this->secondsPerDay * 29) +
  144. ($this->secondsPerHour * 23) +
  145. ($this->secondsPerMinute * 59) +
  146. 29
  147. )
  148. ) {
  149. $days = round($timeDifference / $this->secondsPerDay);
  150. $timeAgo = $this->_translate('days', $days);
  151. }
  152. // rule 8
  153. // between 29days23hours59mins30secs and 59days23hours59mins29secs
  154. else if (
  155. $timeDifference >= (
  156. ($this->secondsPerDay * 29) +
  157. ($this->secondsPerHour * 23) +
  158. ($this->secondsPerMinute * 59) +
  159. 30
  160. )
  161. &&
  162. $timeDifference <= (
  163. ($this->secondsPerDay * 59) +
  164. ($this->secondsPerHour * 23) +
  165. ($this->secondsPerMinute * 59) +
  166. 29
  167. )
  168. ) {
  169. $timeAgo = $this->_translate('aboutOneMonth');
  170. }
  171. // rule 9
  172. // between 59days23hours59mins30secs and 1year (minus 1sec)
  173. else if (
  174. $timeDifference >= (
  175. ($this->secondsPerDay * 59) +
  176. ($this->secondsPerHour * 23) +
  177. ($this->secondsPerMinute * 59) +
  178. 30
  179. )
  180. &&
  181. $timeDifference < $this->secondsPerYear
  182. ) {
  183. $months = round($timeDifference / $this->secondsPerMonth);
  184. // if months is 1, then set it to 2, because we are "past" 1 month
  185. if ($months == 1) {
  186. $months = 2;
  187. }
  188. $timeAgo = $this->_translate('months', $months);
  189. }
  190. // rule 10
  191. // between 1year and 2years (minus 1sec)
  192. else if (
  193. $timeDifference >= $this->secondsPerYear
  194. &&
  195. $timeDifference < ($this->secondsPerYear * 2)
  196. ) {
  197. $timeAgo = $this->_translate('aboutOneYear');
  198. }
  199. // rule 11
  200. // 2years or more
  201. else {
  202. $years = floor($timeDifference / $this->secondsPerYear);
  203. $timeAgo = $this->_translate('years', $years);
  204. }
  205. $this->restoreTimezone();
  206. return $timeAgo;
  207. }
  208. /**
  209. * Fetches the date difference between the two given dates.
  210. * NOTE: both past and now should be parseable by strtotime
  211. *
  212. * @param string $past the "past" time to parse
  213. * @param string $now the "now" time to parse
  214. * @return array the difference in dates, using the two dates
  215. */
  216. public function dateDifference($past, $now = "now")
  217. {
  218. // initializes the placeholders for the different "times"
  219. $seconds = 0;
  220. $minutes = 0;
  221. $hours = 0;
  222. $days = 0;
  223. $months = 0;
  224. $years = 0;
  225. // sets the default timezone
  226. $this->changeTimezone();
  227. // finds the past in datetime
  228. $past = strtotime($past);
  229. // finds the current datetime
  230. $now = strtotime($now);
  231. // calculates the difference
  232. $timeDifference = $now - $past;
  233. // starts determining the time difference
  234. if ($timeDifference >= 0) {
  235. switch ($timeDifference) {
  236. // finds the number of years
  237. case ($timeDifference >= $this->secondsPerYear):
  238. // uses floor to remove decimals
  239. $years = floor($timeDifference / $this->secondsPerYear);
  240. // saves the amount of seconds left
  241. $timeDifference = $timeDifference - ($years * $this->secondsPerYear);
  242. // finds the number of months
  243. case ($timeDifference >= $this->secondsPerMonth && $timeDifference <= ($this->secondsPerYear - 1)):
  244. // uses floor to remove decimals
  245. $months = floor($timeDifference / $this->secondsPerMonth);
  246. // saves the amount of seconds left
  247. $timeDifference = $timeDifference - ($months * $this->secondsPerMonth);
  248. // finds the number of days
  249. case ($timeDifference >= $this->secondsPerDay && $timeDifference <= ($this->secondsPerYear - 1)):
  250. // uses floor to remove decimals
  251. $days = floor($timeDifference / $this->secondsPerDay);
  252. // saves the amount of seconds left
  253. $timeDifference = $timeDifference - ($days * $this->secondsPerDay);
  254. // finds the number of hours
  255. case ($timeDifference >= $this->secondsPerHour && $timeDifference <= ($this->secondsPerDay - 1)):
  256. // uses floor to remove decimals
  257. $hours = floor($timeDifference / $this->secondsPerHour);
  258. // saves the amount of seconds left
  259. $timeDifference = $timeDifference - ($hours * $this->secondsPerHour);
  260. // finds the number of minutes
  261. case ($timeDifference >= $this->secondsPerMinute && $timeDifference <= ($this->secondsPerHour - 1)):
  262. // uses floor to remove decimals
  263. $minutes = floor($timeDifference / $this->secondsPerMinute);
  264. // saves the amount of seconds left
  265. $timeDifference = $timeDifference - ($minutes * $this->secondsPerMinute);
  266. // finds the number of seconds
  267. case ($timeDifference <= ($this->secondsPerMinute - 1)):
  268. // seconds is just what there is in the timeDifference variable
  269. $seconds = $timeDifference;
  270. }
  271. }
  272. $this->restoreTimezone();
  273. $difference = [
  274. "years" => $years,
  275. "months" => $months,
  276. "days" => $days,
  277. "hours" => $hours,
  278. "minutes" => $minutes,
  279. "seconds" => $seconds,
  280. ];
  281. return $difference;
  282. }
  283. /**
  284. * Translates the given $label, and adds the given $time.
  285. * @param string $label the label to translate
  286. * @param string $time the time to add to the translated text.
  287. * @return string the translated label text including the time.
  288. */
  289. protected function _translate($label, $time = '')
  290. {
  291. // handles a usecase introduced in #18, where a new translation was added.
  292. // This would cause an array-out-of-bound exception, since the index does not
  293. // exist in most translations.
  294. if (!isset(self::$timeAgoStrings[$label])) {
  295. return '';
  296. }
  297. return sprintf(self::$timeAgoStrings[$label], $time);
  298. }
  299. /**
  300. * Loads the translations into the system.
  301. */
  302. protected static function _loadTranslations($language, $alternate_path = null)
  303. {
  304. // no time strings loaded? load them and store it all in static variables
  305. if (self::$timeAgoStrings == null || self::$language != $language) {
  306. include(__DIR__ . '/translations/' . $language . '.php');
  307. if (!isset($timeAgoStrings) && $alternate_path) {
  308. include($alternate_path);
  309. }
  310. // storing the time strings in the current object
  311. self::$timeAgoStrings = $timeAgoStrings;
  312. // loads the language files
  313. if (self::$timeAgoStrings == null) {
  314. error_log('Could not load language file for language ' . $language . '. Please ensure that the file exists in ' . __DIR__ . 'translations/' . $language . '.php');
  315. }
  316. }
  317. // storing the language
  318. self::$language = $language;
  319. }
  320. protected function changeTimezone()
  321. {
  322. $this->previousTimezone = false;
  323. if ($this->timezone) {
  324. $this->previousTimezone = date_default_timezone_get();
  325. date_default_timezone_set($this->timezone);
  326. }
  327. }
  328. protected function restoreTimezone()
  329. {
  330. if ($this->previousTimezone) {
  331. date_default_timezone_set($this->previousTimezone);
  332. $this->previousTimezone = false;
  333. }
  334. }
  335. }