Aucune description

humanize.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. (function() {
  2. // Baseline setup
  3. // --------------
  4. // Establish the root object, `window` in the browser, or `global` on the server.
  5. var root = this;
  6. // Save the previous value of the `humanize` variable.
  7. var previousHumanize = root.humanize;
  8. var humanize = {};
  9. if (typeof exports !== 'undefined') {
  10. if (typeof module !== 'undefined' && module.exports) {
  11. exports = module.exports = humanize;
  12. }
  13. exports.humanize = humanize;
  14. } else {
  15. if (typeof define === 'function' && define.amd) {
  16. define('humanize', function() {
  17. return humanize;
  18. });
  19. }
  20. root.humanize = humanize;
  21. }
  22. humanize.noConflict = function() {
  23. root.humanize = previousHumanize;
  24. return this;
  25. };
  26. humanize.pad = function(str, count, padChar, type) {
  27. str += '';
  28. if (!padChar) {
  29. padChar = ' ';
  30. } else if (padChar.length > 1) {
  31. padChar = padChar.charAt(0);
  32. }
  33. type = (type === undefined) ? 'left' : 'right';
  34. if (type === 'right') {
  35. while (str.length < count) {
  36. str = str + padChar;
  37. }
  38. } else {
  39. // default to left
  40. while (str.length < count) {
  41. str = padChar + str;
  42. }
  43. }
  44. return str;
  45. };
  46. // gets current unix time
  47. humanize.time = function() {
  48. return new Date().getTime() / 1000;
  49. };
  50. /**
  51. * PHP-inspired date
  52. */
  53. /* jan feb mar apr may jun jul aug sep oct nov dec */
  54. var dayTableCommon = [ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ];
  55. var dayTableLeap = [ 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 ];
  56. // var mtable_common[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  57. // static int ml_table_leap[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  58. humanize.date = function(format, timestamp) {
  59. var jsdate = ((timestamp === undefined) ? new Date() : // Not provided
  60. (timestamp instanceof Date) ? new Date(timestamp) : // JS Date()
  61. new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int)
  62. );
  63. var formatChr = /\\?([a-z])/gi;
  64. var formatChrCb = function (t, s) {
  65. return f[t] ? f[t]() : s;
  66. };
  67. var shortDayTxt = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
  68. var monthTxt = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  69. var f = {
  70. /* Day */
  71. // Day of month w/leading 0; 01..31
  72. d: function () { return humanize.pad(f.j(), 2, '0'); },
  73. // Shorthand day name; Mon..Sun
  74. D: function () { return f.l().slice(0, 3); },
  75. // Day of month; 1..31
  76. j: function () { return jsdate.getDate(); },
  77. // Full day name; Monday..Sunday
  78. l: function () { return shortDayTxt[f.w()]; },
  79. // ISO-8601 day of week; 1[Mon]..7[Sun]
  80. N: function () { return f.w() || 7; },
  81. // Ordinal suffix for day of month; st, nd, rd, th
  82. S: function () {
  83. var j = f.j();
  84. return j > 4 && j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th';
  85. },
  86. // Day of week; 0[Sun]..6[Sat]
  87. w: function () { return jsdate.getDay(); },
  88. // Day of year; 0..365
  89. z: function () {
  90. return (f.L() ? dayTableLeap[f.n()] : dayTableCommon[f.n()]) + f.j() - 1;
  91. },
  92. /* Week */
  93. // ISO-8601 week number
  94. W: function () {
  95. // days between midweek of this week and jan 4
  96. // (f.z() - f.N() + 1 + 3.5) - 3
  97. var midWeekDaysFromJan4 = f.z() - f.N() + 1.5;
  98. // 1 + number of weeks + rounded week
  99. return humanize.pad(1 + Math.floor(Math.abs(midWeekDaysFromJan4) / 7) + (midWeekDaysFromJan4 % 7 > 3.5 ? 1 : 0), 2, '0');
  100. },
  101. /* Month */
  102. // Full month name; January..December
  103. F: function () { return monthTxt[jsdate.getMonth()]; },
  104. // Month w/leading 0; 01..12
  105. m: function () { return humanize.pad(f.n(), 2, '0'); },
  106. // Shorthand month name; Jan..Dec
  107. M: function () { return f.F().slice(0, 3); },
  108. // Month; 1..12
  109. n: function () { return jsdate.getMonth() + 1; },
  110. // Days in month; 28..31
  111. t: function () { return (new Date(f.Y(), f.n(), 0)).getDate(); },
  112. /* Year */
  113. // Is leap year?; 0 or 1
  114. L: function () { return new Date(f.Y(), 1, 29).getMonth() === 1 ? 1 : 0; },
  115. // ISO-8601 year
  116. o: function () {
  117. var n = f.n();
  118. var W = f.W();
  119. return f.Y() + (n === 12 && W < 9 ? -1 : n === 1 && W > 9);
  120. },
  121. // Full year; e.g. 1980..2010
  122. Y: function () { return jsdate.getFullYear(); },
  123. // Last two digits of year; 00..99
  124. y: function () { return (String(f.Y())).slice(-2); },
  125. /* Time */
  126. // am or pm
  127. a: function () { return jsdate.getHours() > 11 ? 'pm' : 'am'; },
  128. // AM or PM
  129. A: function () { return f.a().toUpperCase(); },
  130. // Swatch Internet time; 000..999
  131. B: function () {
  132. var unixTime = jsdate.getTime() / 1000;
  133. var secondsPassedToday = unixTime % 86400 + 3600; // since it's based off of UTC+1
  134. if (secondsPassedToday < 0) { secondsPassedToday += 86400; }
  135. var beats = ((secondsPassedToday) / 86.4) % 1000;
  136. if (unixTime < 0) {
  137. return Math.ceil(beats);
  138. }
  139. return Math.floor(beats);
  140. },
  141. // 12-Hours; 1..12
  142. g: function () { return f.G() % 12 || 12; },
  143. // 24-Hours; 0..23
  144. G: function () { return jsdate.getHours(); },
  145. // 12-Hours w/leading 0; 01..12
  146. h: function () { return humanize.pad(f.g(), 2, '0'); },
  147. // 24-Hours w/leading 0; 00..23
  148. H: function () { return humanize.pad(f.G(), 2, '0'); },
  149. // Minutes w/leading 0; 00..59
  150. i: function () { return humanize.pad(jsdate.getMinutes(), 2, '0'); },
  151. // Seconds w/leading 0; 00..59
  152. s: function () { return humanize.pad(jsdate.getSeconds(), 2, '0'); },
  153. // Microseconds; 000000-999000
  154. u: function () { return humanize.pad(jsdate.getMilliseconds() * 1000, 6, '0'); },
  155. // Whether or not the date is in daylight savings time
  156. /*
  157. I: function () {
  158. // Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC.
  159. // If they are not equal, then DST is observed.
  160. var Y = f.Y();
  161. return 0 + ((new Date(Y, 0) - Date.UTC(Y, 0)) !== (new Date(Y, 6) - Date.UTC(Y, 6)));
  162. },
  163. */
  164. // Difference to GMT in hour format; e.g. +0200
  165. O: function () {
  166. var tzo = jsdate.getTimezoneOffset();
  167. var tzoNum = Math.abs(tzo);
  168. return (tzo > 0 ? '-' : '+') + humanize.pad(Math.floor(tzoNum / 60) * 100 + tzoNum % 60, 4, '0');
  169. },
  170. // Difference to GMT w/colon; e.g. +02:00
  171. P: function () {
  172. var O = f.O();
  173. return (O.substr(0, 3) + ':' + O.substr(3, 2));
  174. },
  175. // Timezone offset in seconds (-43200..50400)
  176. Z: function () { return -jsdate.getTimezoneOffset() * 60; },
  177. // Full Date/Time, ISO-8601 date
  178. c: function () { return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); },
  179. // RFC 2822
  180. r: function () { return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); },
  181. // Seconds since UNIX epoch
  182. U: function () { return jsdate.getTime() / 1000 || 0; }
  183. };
  184. return format.replace(formatChr, formatChrCb);
  185. };
  186. /**
  187. * format number by adding thousands separaters and significant digits while rounding
  188. */
  189. humanize.numberFormat = function(number, decimals, decPoint, thousandsSep) {
  190. decimals = isNaN(decimals) ? 2 : Math.abs(decimals);
  191. decPoint = (decPoint === undefined) ? '.' : decPoint;
  192. thousandsSep = (thousandsSep === undefined) ? ',' : thousandsSep;
  193. var sign = number < 0 ? '-' : '';
  194. number = Math.abs(+number || 0);
  195. var intPart = parseInt(number.toFixed(decimals), 10) + '';
  196. var j = intPart.length > 3 ? intPart.length % 3 : 0;
  197. return sign + (j ? intPart.substr(0, j) + thousandsSep : '') + intPart.substr(j).replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep) + (decimals ? decPoint + Math.abs(number - intPart).toFixed(decimals).slice(2) : '');
  198. };
  199. /**
  200. * For dates that are the current day or within one day, return 'today', 'tomorrow' or 'yesterday', as appropriate.
  201. * Otherwise, format the date using the passed in format string.
  202. *
  203. * Examples (when 'today' is 17 Feb 2007):
  204. * 16 Feb 2007 becomes yesterday.
  205. * 17 Feb 2007 becomes today.
  206. * 18 Feb 2007 becomes tomorrow.
  207. * Any other day is formatted according to given argument or the DATE_FORMAT setting if no argument is given.
  208. */
  209. humanize.naturalDay = function(timestamp, format) {
  210. timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
  211. format = (format === undefined) ? 'Y-m-d' : format;
  212. var oneDay = 86400;
  213. var d = new Date();
  214. var today = (new Date(d.getFullYear(), d.getMonth(), d.getDate())).getTime() / 1000;
  215. if (timestamp < today && timestamp >= today - oneDay) {
  216. return 'yesterday';
  217. } else if (timestamp >= today && timestamp < today + oneDay) {
  218. return 'today';
  219. } else if (timestamp >= today + oneDay && timestamp < today + 2 * oneDay) {
  220. return 'tomorrow';
  221. }
  222. return humanize.date(format, timestamp);
  223. };
  224. /**
  225. * returns a string representing how many seconds, minutes or hours ago it was or will be in the future
  226. * Will always return a relative time, most granular of seconds to least granular of years. See unit tests for more details
  227. */
  228. humanize.relativeTime = function(timestamp) {
  229. timestamp = (timestamp === undefined) ? humanize.time() : timestamp;
  230. var currTime = humanize.time();
  231. var timeDiff = currTime - timestamp;
  232. // within 2 seconds
  233. if (timeDiff < 2 && timeDiff > -2) {
  234. return (timeDiff >= 0 ? 'just ' : '') + 'now';
  235. }
  236. // within a minute
  237. if (timeDiff < 60 && timeDiff > -60) {
  238. return (timeDiff >= 0 ? Math.floor(timeDiff) + ' seconds ago' : 'in ' + Math.floor(-timeDiff) + ' seconds');
  239. }
  240. // within 2 minutes
  241. if (timeDiff < 120 && timeDiff > -120) {
  242. return (timeDiff >= 0 ? 'about a minute ago' : 'in about a minute');
  243. }
  244. // within an hour
  245. if (timeDiff < 3600 && timeDiff > -3600) {
  246. return (timeDiff >= 0 ? Math.floor(timeDiff / 60) + ' minutes ago' : 'in ' + Math.floor(-timeDiff / 60) + ' minutes');
  247. }
  248. // within 2 hours
  249. if (timeDiff < 7200 && timeDiff > -7200) {
  250. return (timeDiff >= 0 ? 'about an hour ago' : 'in about an hour');
  251. }
  252. // within 24 hours
  253. if (timeDiff < 86400 && timeDiff > -86400) {
  254. return (timeDiff >= 0 ? Math.floor(timeDiff / 3600) + ' hours ago' : 'in ' + Math.floor(-timeDiff / 3600) + ' hours');
  255. }
  256. // within 2 days
  257. var days2 = 2 * 86400;
  258. if (timeDiff < days2 && timeDiff > -days2) {
  259. return (timeDiff >= 0 ? '1 day ago' : 'in 1 day');
  260. }
  261. // within 29 days
  262. var days29 = 29 * 86400;
  263. if (timeDiff < days29 && timeDiff > -days29) {
  264. return (timeDiff >= 0 ? Math.floor(timeDiff / 86400) + ' days ago' : 'in ' + Math.floor(-timeDiff / 86400) + ' days');
  265. }
  266. // within 60 days
  267. var days60 = 60 * 86400;
  268. if (timeDiff < days60 && timeDiff > -days60) {
  269. return (timeDiff >= 0 ? 'about a month ago' : 'in about a month');
  270. }
  271. var currTimeYears = parseInt(humanize.date('Y', currTime), 10);
  272. var timestampYears = parseInt(humanize.date('Y', timestamp), 10);
  273. var currTimeMonths = currTimeYears * 12 + parseInt(humanize.date('n', currTime), 10);
  274. var timestampMonths = timestampYears * 12 + parseInt(humanize.date('n', timestamp), 10);
  275. // within a year
  276. var monthDiff = currTimeMonths - timestampMonths;
  277. if (monthDiff < 12 && monthDiff > -12) {
  278. return (monthDiff >= 0 ? monthDiff + ' months ago' : 'in ' + (-monthDiff) + ' months');
  279. }
  280. var yearDiff = currTimeYears - timestampYears;
  281. if (yearDiff < 2 && yearDiff > -2) {
  282. return (yearDiff >= 0 ? 'a year ago' : 'in a year');
  283. }
  284. return (yearDiff >= 0 ? yearDiff + ' years ago' : 'in ' + (-yearDiff) + ' years');
  285. };
  286. /**
  287. * Converts an integer to its ordinal as a string.
  288. *
  289. * 1 becomes 1st
  290. * 2 becomes 2nd
  291. * 3 becomes 3rd etc
  292. */
  293. humanize.ordinal = function(number) {
  294. number = parseInt(number, 10);
  295. number = isNaN(number) ? 0 : number;
  296. var sign = number < 0 ? '-' : '';
  297. number = Math.abs(number);
  298. var tens = number % 100;
  299. return sign + number + (tens > 4 && tens < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[number % 10] || 'th');
  300. };
  301. /**
  302. * Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc).
  303. *
  304. * For example:
  305. * If value is 123456789, the output would be 117.7 MB.
  306. */
  307. humanize.filesize = function(filesize, kilo, decimals, decPoint, thousandsSep, suffixSep) {
  308. kilo = (kilo === undefined) ? 1024 : kilo;
  309. if (filesize <= 0) { return '0 bytes'; }
  310. if (filesize < kilo && decimals === undefined) { decimals = 0; }
  311. if (suffixSep === undefined) { suffixSep = ' '; }
  312. return humanize.intword(filesize, ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'], kilo, decimals, decPoint, thousandsSep, suffixSep);
  313. };
  314. /**
  315. * Formats the value like a 'human-readable' number (i.e. '13 K', '4.1 M', '102', etc).
  316. *
  317. * For example:
  318. * If value is 123456789, the output would be 117.7 M.
  319. */
  320. humanize.intword = function(number, units, kilo, decimals, decPoint, thousandsSep, suffixSep) {
  321. var humanized, unit;
  322. units = units || ['', 'K', 'M', 'B', 'T'],
  323. unit = units.length - 1,
  324. kilo = kilo || 1000,
  325. decimals = isNaN(decimals) ? 2 : Math.abs(decimals),
  326. decPoint = decPoint || '.',
  327. thousandsSep = thousandsSep || ',',
  328. suffixSep = suffixSep || '';
  329. for (var i=0; i < units.length; i++) {
  330. if (number < Math.pow(kilo, i+1)) {
  331. unit = i;
  332. break;
  333. }
  334. }
  335. humanized = number / Math.pow(kilo, unit);
  336. var suffix = units[unit] ? suffixSep + units[unit] : '';
  337. return humanize.numberFormat(humanized, decimals, decPoint, thousandsSep) + suffix;
  338. };
  339. /**
  340. * Replaces line breaks in plain text with appropriate HTML
  341. * A single newline becomes an HTML line break (<br />) and a new line followed by a blank line becomes a paragraph break (</p>).
  342. *
  343. * For example:
  344. * If value is Joel\nis a\n\nslug, the output will be <p>Joel<br />is a</p><p>slug</p>
  345. */
  346. humanize.linebreaks = function(str) {
  347. // remove beginning and ending newlines
  348. str = str.replace(/^([\n|\r]*)/, '');
  349. str = str.replace(/([\n|\r]*)$/, '');
  350. // normalize all to \n
  351. str = str.replace(/(\r\n|\n|\r)/g, "\n");
  352. // any consecutive new lines more than 2 gets turned into p tags
  353. str = str.replace(/(\n{2,})/g, '</p><p>');
  354. // any that are singletons get turned into br
  355. str = str.replace(/\n/g, '<br />');
  356. return '<p>' + str + '</p>';
  357. };
  358. /**
  359. * Converts all newlines in a piece of plain text to HTML line breaks (<br />).
  360. */
  361. humanize.nl2br = function(str) {
  362. return str.replace(/(\r\n|\n|\r)/g, '<br />');
  363. };
  364. /**
  365. * Truncates a string if it is longer than the specified number of characters.
  366. * Truncated strings will end with a translatable ellipsis sequence ('…').
  367. */
  368. humanize.truncatechars = function(string, length) {
  369. if (string.length <= length) { return string; }
  370. return string.substr(0, length) + '…';
  371. };
  372. /**
  373. * Truncates a string after a certain number of words.
  374. * Newlines within the string will be removed.
  375. */
  376. humanize.truncatewords = function(string, numWords) {
  377. var words = string.split(' ');
  378. if (words.length < numWords) { return string; }
  379. return words.slice(0, numWords).join(' ') + '…';
  380. };
  381. }).call(this);