You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
17 KiB
496 lines
17 KiB
/* |
|
Copyright (c) 2012 Tai-Jin Lee http://www.taijinlee.com |
|
|
|
Permission is hereby granted, free of charge, to any person obtaining |
|
a copy of this software and associated documentation files (the |
|
"Software"), to deal in the Software without restriction, including |
|
without limitation the rights to use, copy, modify, merge, publish, |
|
distribute, sublicense, and/or sell copies of the Software, and to |
|
permit persons to whom the Software is furnished to do so, subject to |
|
the following conditions: |
|
|
|
The above copyright notice and this permission notice shall be |
|
included in all copies or substantial portions of the Software. |
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
*/ |
|
|
|
(function() { |
|
|
|
// Baseline setup |
|
// -------------- |
|
|
|
// Establish the root object, `window` in the browser, or `global` on the server. |
|
var root = this; |
|
|
|
// Save the previous value of the `humanize` variable. |
|
var previousHumanize = root.humanize; |
|
|
|
var humanize = {}; |
|
|
|
if (typeof exports !== 'undefined') { |
|
if (typeof module !== 'undefined' && module.exports) { |
|
exports = module.exports = humanize; |
|
} |
|
exports.humanize = humanize; |
|
} else { |
|
if (typeof define === 'function' && define.amd) { |
|
define('humanize', function() { |
|
return humanize; |
|
}); |
|
} |
|
root.humanize = humanize; |
|
} |
|
|
|
humanize.noConflict = function() { |
|
root.humanize = previousHumanize; |
|
return this; |
|
}; |
|
|
|
humanize.pad = function(str, count, padChar, type) { |
|
str += ''; |
|
if (!padChar) { |
|
padChar = ' '; |
|
} else if (padChar.length > 1) { |
|
padChar = padChar.charAt(0); |
|
} |
|
type = (type === undefined) ? 'left' : 'right'; |
|
|
|
if (type === 'right') { |
|
while (str.length < count) { |
|
str = str + padChar; |
|
} |
|
} else { |
|
// default to left |
|
while (str.length < count) { |
|
str = padChar + str; |
|
} |
|
} |
|
|
|
return str; |
|
}; |
|
|
|
// gets current unix time |
|
humanize.time = function() { |
|
return new Date().getTime() / 1000; |
|
}; |
|
|
|
/** |
|
* PHP-inspired date |
|
*/ |
|
|
|
/* jan feb mar apr may jun jul aug sep oct nov dec */ |
|
var dayTableCommon = [ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 ]; |
|
var dayTableLeap = [ 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 ]; |
|
// var mtable_common[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
|
// static int ml_table_leap[13] = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
|
|
|
|
|
humanize.date = function(format, timestamp) { |
|
var jsdate = ((timestamp === undefined) ? new Date() : // Not provided |
|
(timestamp instanceof Date) ? new Date(timestamp) : // JS Date() |
|
new Date(timestamp * 1000) // UNIX timestamp (auto-convert to int) |
|
); |
|
|
|
var formatChr = /\\?([a-z])/gi; |
|
var formatChrCb = function (t, s) { |
|
return f[t] ? f[t]() : s; |
|
}; |
|
|
|
var shortDayTxt = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; |
|
var monthTxt = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; |
|
|
|
var f = { |
|
/* Day */ |
|
// Day of month w/leading 0; 01..31 |
|
d: function () { return humanize.pad(f.j(), 2, '0'); }, |
|
|
|
// Shorthand day name; Mon..Sun |
|
D: function () { return f.l().slice(0, 3); }, |
|
|
|
// Day of month; 1..31 |
|
j: function () { return jsdate.getDate(); }, |
|
|
|
// Full day name; Monday..Sunday |
|
l: function () { return shortDayTxt[f.w()]; }, |
|
|
|
// ISO-8601 day of week; 1[Mon]..7[Sun] |
|
N: function () { return f.w() || 7; }, |
|
|
|
// Ordinal suffix for day of month; st, nd, rd, th |
|
S: function () { |
|
var j = f.j(); |
|
return j > 4 && j < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[j % 10] || 'th'; |
|
}, |
|
|
|
// Day of week; 0[Sun]..6[Sat] |
|
w: function () { return jsdate.getDay(); }, |
|
|
|
// Day of year; 0..365 |
|
z: function () { |
|
return (f.L() ? dayTableLeap[f.n()] : dayTableCommon[f.n()]) + f.j() - 1; |
|
}, |
|
|
|
/* Week */ |
|
// ISO-8601 week number |
|
W: function () { |
|
// days between midweek of this week and jan 4 |
|
// (f.z() - f.N() + 1 + 3.5) - 3 |
|
var midWeekDaysFromJan4 = f.z() - f.N() + 1.5; |
|
// 1 + number of weeks + rounded week |
|
return humanize.pad(1 + Math.floor(Math.abs(midWeekDaysFromJan4) / 7) + (midWeekDaysFromJan4 % 7 > 3.5 ? 1 : 0), 2, '0'); |
|
}, |
|
|
|
/* Month */ |
|
// Full month name; January..December |
|
F: function () { return monthTxt[jsdate.getMonth()]; }, |
|
|
|
// Month w/leading 0; 01..12 |
|
m: function () { return humanize.pad(f.n(), 2, '0'); }, |
|
|
|
// Shorthand month name; Jan..Dec |
|
M: function () { return f.F().slice(0, 3); }, |
|
|
|
// Month; 1..12 |
|
n: function () { return jsdate.getMonth() + 1; }, |
|
|
|
// Days in month; 28..31 |
|
t: function () { return (new Date(f.Y(), f.n(), 0)).getDate(); }, |
|
|
|
/* Year */ |
|
// Is leap year?; 0 or 1 |
|
L: function () { return new Date(f.Y(), 1, 29).getMonth() === 1 ? 1 : 0; }, |
|
|
|
// ISO-8601 year |
|
o: function () { |
|
var n = f.n(); |
|
var W = f.W(); |
|
return f.Y() + (n === 12 && W < 9 ? -1 : n === 1 && W > 9); |
|
}, |
|
|
|
// Full year; e.g. 1980..2010 |
|
Y: function () { return jsdate.getFullYear(); }, |
|
|
|
// Last two digits of year; 00..99 |
|
y: function () { return (String(f.Y())).slice(-2); }, |
|
|
|
/* Time */ |
|
// am or pm |
|
a: function () { return jsdate.getHours() > 11 ? 'pm' : 'am'; }, |
|
|
|
// AM or PM |
|
A: function () { return f.a().toUpperCase(); }, |
|
|
|
// Swatch Internet time; 000..999 |
|
B: function () { |
|
var unixTime = jsdate.getTime() / 1000; |
|
var secondsPassedToday = unixTime % 86400 + 3600; // since it's based off of UTC+1 |
|
if (secondsPassedToday < 0) { secondsPassedToday += 86400; } |
|
var beats = ((secondsPassedToday) / 86.4) % 1000; |
|
if (unixTime < 0) { |
|
return Math.ceil(beats); |
|
} |
|
return Math.floor(beats); |
|
}, |
|
|
|
// 12-Hours; 1..12 |
|
g: function () { return f.G() % 12 || 12; }, |
|
|
|
// 24-Hours; 0..23 |
|
G: function () { return jsdate.getHours(); }, |
|
|
|
// 12-Hours w/leading 0; 01..12 |
|
h: function () { return humanize.pad(f.g(), 2, '0'); }, |
|
|
|
// 24-Hours w/leading 0; 00..23 |
|
H: function () { return humanize.pad(f.G(), 2, '0'); }, |
|
|
|
// Minutes w/leading 0; 00..59 |
|
i: function () { return humanize.pad(jsdate.getMinutes(), 2, '0'); }, |
|
|
|
// Seconds w/leading 0; 00..59 |
|
s: function () { return humanize.pad(jsdate.getSeconds(), 2, '0'); }, |
|
|
|
// Microseconds; 000000-999000 |
|
u: function () { return humanize.pad(jsdate.getMilliseconds() * 1000, 6, '0'); }, |
|
|
|
// Whether or not the date is in daylight savings time |
|
/* |
|
I: function () { |
|
// Compares Jan 1 minus Jan 1 UTC to Jul 1 minus Jul 1 UTC. |
|
// If they are not equal, then DST is observed. |
|
var Y = f.Y(); |
|
return 0 + ((new Date(Y, 0) - Date.UTC(Y, 0)) !== (new Date(Y, 6) - Date.UTC(Y, 6))); |
|
}, |
|
*/ |
|
|
|
// Difference to GMT in hour format; e.g. +0200 |
|
O: function () { |
|
var tzo = jsdate.getTimezoneOffset(); |
|
var tzoNum = Math.abs(tzo); |
|
return (tzo > 0 ? '-' : '+') + humanize.pad(Math.floor(tzoNum / 60) * 100 + tzoNum % 60, 4, '0'); |
|
}, |
|
|
|
// Difference to GMT w/colon; e.g. +02:00 |
|
P: function () { |
|
var O = f.O(); |
|
return (O.substr(0, 3) + ':' + O.substr(3, 2)); |
|
}, |
|
|
|
// Timezone offset in seconds (-43200..50400) |
|
Z: function () { return -jsdate.getTimezoneOffset() * 60; }, |
|
|
|
// Full Date/Time, ISO-8601 date |
|
c: function () { return 'Y-m-d\\TH:i:sP'.replace(formatChr, formatChrCb); }, |
|
|
|
// RFC 2822 |
|
r: function () { return 'D, d M Y H:i:s O'.replace(formatChr, formatChrCb); }, |
|
|
|
// Seconds since UNIX epoch |
|
U: function () { return jsdate.getTime() / 1000 || 0; } |
|
}; |
|
|
|
return format.replace(formatChr, formatChrCb); |
|
}; |
|
|
|
|
|
/** |
|
* format number by adding thousands separaters and significant digits while rounding |
|
*/ |
|
humanize.numberFormat = function(number, decimals, decPoint, thousandsSep) { |
|
decimals = isNaN(decimals) ? 2 : Math.abs(decimals); |
|
decPoint = (decPoint === undefined) ? '.' : decPoint; |
|
thousandsSep = (thousandsSep === undefined) ? ',' : thousandsSep; |
|
|
|
var sign = number < 0 ? '-' : ''; |
|
number = Math.abs(+number || 0); |
|
|
|
var intPart = parseInt(number.toFixed(decimals), 10) + ''; |
|
var j = intPart.length > 3 ? intPart.length % 3 : 0; |
|
|
|
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) : ''); |
|
}; |
|
|
|
|
|
/** |
|
* For dates that are the current day or within one day, return 'today', 'tomorrow' or 'yesterday', as appropriate. |
|
* Otherwise, format the date using the passed in format string. |
|
* |
|
* Examples (when 'today' is 17 Feb 2007): |
|
* 16 Feb 2007 becomes yesterday. |
|
* 17 Feb 2007 becomes today. |
|
* 18 Feb 2007 becomes tomorrow. |
|
* Any other day is formatted according to given argument or the DATE_FORMAT setting if no argument is given. |
|
*/ |
|
humanize.naturalDay = function(timestamp, format) { |
|
timestamp = (timestamp === undefined) ? humanize.time() : timestamp; |
|
format = (format === undefined) ? 'Y-m-d' : format; |
|
|
|
var oneDay = 86400; |
|
var d = new Date(); |
|
var today = (new Date(d.getFullYear(), d.getMonth(), d.getDate())).getTime() / 1000; |
|
|
|
if (timestamp < today && timestamp >= today - oneDay) { |
|
return 'yesterday'; |
|
} else if (timestamp >= today && timestamp < today + oneDay) { |
|
return 'today'; |
|
} else if (timestamp >= today + oneDay && timestamp < today + 2 * oneDay) { |
|
return 'tomorrow'; |
|
} |
|
|
|
return humanize.date(format, timestamp); |
|
}; |
|
|
|
/** |
|
* returns a string representing how many seconds, minutes or hours ago it was or will be in the future |
|
* Will always return a relative time, most granular of seconds to least granular of years. See unit tests for more details |
|
*/ |
|
humanize.relativeTime = function(timestamp) { |
|
timestamp = (timestamp === undefined) ? humanize.time() : timestamp; |
|
|
|
var currTime = humanize.time(); |
|
var timeDiff = currTime - timestamp; |
|
|
|
// within 2 seconds |
|
if (timeDiff < 2 && timeDiff > -2) { |
|
return (timeDiff >= 0 ? 'just ' : '') + 'now'; |
|
} |
|
|
|
// within a minute |
|
if (timeDiff < 60 && timeDiff > -60) { |
|
return (timeDiff >= 0 ? Math.floor(timeDiff) + ' seconds ago' : 'in ' + Math.floor(-timeDiff) + ' seconds'); |
|
} |
|
|
|
// within 2 minutes |
|
if (timeDiff < 120 && timeDiff > -120) { |
|
return (timeDiff >= 0 ? 'about a minute ago' : 'in about a minute'); |
|
} |
|
|
|
// within an hour |
|
if (timeDiff < 3600 && timeDiff > -3600) { |
|
return (timeDiff >= 0 ? Math.floor(timeDiff / 60) + ' minutes ago' : 'in ' + Math.floor(-timeDiff / 60) + ' minutes'); |
|
} |
|
|
|
// within 2 hours |
|
if (timeDiff < 7200 && timeDiff > -7200) { |
|
return (timeDiff >= 0 ? 'about an hour ago' : 'in about an hour'); |
|
} |
|
|
|
// within 24 hours |
|
if (timeDiff < 86400 && timeDiff > -86400) { |
|
return (timeDiff >= 0 ? Math.floor(timeDiff / 3600) + ' hours ago' : 'in ' + Math.floor(-timeDiff / 3600) + ' hours'); |
|
} |
|
|
|
// within 2 days |
|
var days2 = 2 * 86400; |
|
if (timeDiff < days2 && timeDiff > -days2) { |
|
return (timeDiff >= 0 ? '1 day ago' : 'in 1 day'); |
|
} |
|
|
|
// within 29 days |
|
var days29 = 29 * 86400; |
|
if (timeDiff < days29 && timeDiff > -days29) { |
|
return (timeDiff >= 0 ? Math.floor(timeDiff / 86400) + ' days ago' : 'in ' + Math.floor(-timeDiff / 86400) + ' days'); |
|
} |
|
|
|
// within 60 days |
|
var days60 = 60 * 86400; |
|
if (timeDiff < days60 && timeDiff > -days60) { |
|
return (timeDiff >= 0 ? 'about a month ago' : 'in about a month'); |
|
} |
|
|
|
var currTimeYears = parseInt(humanize.date('Y', currTime), 10); |
|
var timestampYears = parseInt(humanize.date('Y', timestamp), 10); |
|
var currTimeMonths = currTimeYears * 12 + parseInt(humanize.date('n', currTime), 10); |
|
var timestampMonths = timestampYears * 12 + parseInt(humanize.date('n', timestamp), 10); |
|
|
|
// within a year |
|
var monthDiff = currTimeMonths - timestampMonths; |
|
if (monthDiff < 12 && monthDiff > -12) { |
|
return (monthDiff >= 0 ? monthDiff + ' months ago' : 'in ' + (-monthDiff) + ' months'); |
|
} |
|
|
|
var yearDiff = currTimeYears - timestampYears; |
|
if (yearDiff < 2 && yearDiff > -2) { |
|
return (yearDiff >= 0 ? 'a year ago' : 'in a year'); |
|
} |
|
|
|
return (yearDiff >= 0 ? yearDiff + ' years ago' : 'in ' + (-yearDiff) + ' years'); |
|
}; |
|
|
|
/** |
|
* Converts an integer to its ordinal as a string. |
|
* |
|
* 1 becomes 1st |
|
* 2 becomes 2nd |
|
* 3 becomes 3rd etc |
|
*/ |
|
humanize.ordinal = function(number) { |
|
number = parseInt(number, 10); |
|
number = isNaN(number) ? 0 : number; |
|
var sign = number < 0 ? '-' : ''; |
|
number = Math.abs(number); |
|
var tens = number % 100; |
|
|
|
return sign + number + (tens > 4 && tens < 21 ? 'th' : {1: 'st', 2: 'nd', 3: 'rd'}[number % 10] || 'th'); |
|
}; |
|
|
|
/** |
|
* Formats the value like a 'human-readable' file size (i.e. '13 KB', '4.1 MB', '102 bytes', etc). |
|
* |
|
* For example: |
|
* If value is 123456789, the output would be 117.7 MB. |
|
*/ |
|
humanize.filesize = function(filesize, kilo, decimals, decPoint, thousandsSep, suffixSep) { |
|
kilo = (kilo === undefined) ? 1024 : kilo; |
|
if (filesize <= 0) { return '0 bytes'; } |
|
if (filesize < kilo && decimals === undefined) { decimals = 0; } |
|
if (suffixSep === undefined) { suffixSep = ' '; } |
|
return humanize.intword(filesize, ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'], kilo, decimals, decPoint, thousandsSep, suffixSep); |
|
}; |
|
|
|
/** |
|
* Formats the value like a 'human-readable' number (i.e. '13 K', '4.1 M', '102', etc). |
|
* |
|
* For example: |
|
* If value is 123456789, the output would be 117.7 M. |
|
*/ |
|
humanize.intword = function(number, units, kilo, decimals, decPoint, thousandsSep, suffixSep) { |
|
var humanized, unit; |
|
|
|
units = units || ['', 'K', 'M', 'B', 'T'], |
|
unit = units.length - 1, |
|
kilo = kilo || 1000, |
|
decimals = isNaN(decimals) ? 2 : Math.abs(decimals), |
|
decPoint = decPoint || '.', |
|
thousandsSep = thousandsSep || ',', |
|
suffixSep = suffixSep || ''; |
|
|
|
for (var i=0; i < units.length; i++) { |
|
if (number < Math.pow(kilo, i+1)) { |
|
unit = i; |
|
break; |
|
} |
|
} |
|
humanized = number / Math.pow(kilo, unit); |
|
|
|
var suffix = units[unit] ? suffixSep + units[unit] : ''; |
|
return humanize.numberFormat(humanized, decimals, decPoint, thousandsSep) + suffix; |
|
}; |
|
|
|
/** |
|
* Replaces line breaks in plain text with appropriate HTML |
|
* A single newline becomes an HTML line break (<br />) and a new line followed by a blank line becomes a paragraph break (</p>). |
|
* |
|
* For example: |
|
* If value is Joel\nis a\n\nslug, the output will be <p>Joel<br />is a</p><p>slug</p> |
|
*/ |
|
humanize.linebreaks = function(str) { |
|
// remove beginning and ending newlines |
|
str = str.replace(/^([\n|\r]*)/, ''); |
|
str = str.replace(/([\n|\r]*)$/, ''); |
|
|
|
// normalize all to \n |
|
str = str.replace(/(\r\n|\n|\r)/g, "\n"); |
|
|
|
// any consecutive new lines more than 2 gets turned into p tags |
|
str = str.replace(/(\n{2,})/g, '</p><p>'); |
|
|
|
// any that are singletons get turned into br |
|
str = str.replace(/\n/g, '<br />'); |
|
return '<p>' + str + '</p>'; |
|
}; |
|
|
|
/** |
|
* Converts all newlines in a piece of plain text to HTML line breaks (<br />). |
|
*/ |
|
humanize.nl2br = function(str) { |
|
return str.replace(/(\r\n|\n|\r)/g, '<br />'); |
|
}; |
|
|
|
/** |
|
* Truncates a string if it is longer than the specified number of characters. |
|
* Truncated strings will end with a translatable ellipsis sequence ('…'). |
|
*/ |
|
humanize.truncatechars = function(string, length) { |
|
if (string.length <= length) { return string; } |
|
return string.substr(0, length) + '…'; |
|
}; |
|
|
|
/** |
|
* Truncates a string after a certain number of words. |
|
* Newlines within the string will be removed. |
|
*/ |
|
humanize.truncatewords = function(string, numWords) { |
|
var words = string.split(' '); |
|
if (words.length < numWords) { return string; } |
|
return words.slice(0, numWords).join(' ') + '…'; |
|
}; |
|
|
|
}).call(this);
|
|
|