'use strict'; /*@ Ox.formatArea Formats a number of meters as square meters or kilometers > Ox.formatArea(1000) '1,000 m\u00B2' > Ox.formatArea(1000000) '1 km\u00B2' @*/ Ox.formatArea = function(num, dec) { dec = Ox.isUndefined(dec) ? 8 : dec; var km = num >= 1000000; return Ox.formatNumber( (km ? num / 1000000 : num).toPrecision(dec) ) + ' ' + (km ? 'k' : '') + 'm\u00B2'; }; /*@ Ox.formatCurrency Formats a number with a currency symbol > Ox.formatCurrency(1000, '$', 2) '$1,000.00' @*/ Ox.formatCurrency = function(num, str, dec) { return str + Ox.formatNumber(num, dec); }; /*@ Ox.formatDate Formats a date according to a format string See strftime and ISO 8601. '%Q' (quarter) and '%X'/'%x' (year with 'BC'/'AD') are non-standard > Ox.formatDate(Ox.test.date, '%A') // Full weekday 'Sunday' > Ox.formatDate(Ox.test.date, '%a') // Abbreviated weekday 'Sun' > Ox.formatDate(Ox.test.date, '%B') // Full month 'January' > Ox.formatDate(Ox.test.date, '%b') // Abbreviated month 'Jan' > Ox.formatDate(Ox.test.date, '%C') // Century '20' > Ox.formatDate(Ox.test.date, '%c') // US time and date '01/02/05 12:03:04 AM' > Ox.formatDate(Ox.test.date, '%D') // US date '01/02/05' > Ox.formatDate(Ox.test.date, '%d') // Zero-padded day of the month '02' > Ox.formatDate(Ox.test.date, '%e') // Space-padded day of the month ' 2' > Ox.formatDate(Ox.test.date, '%F') // Date '2005-01-02' > Ox.formatDate(Ox.test.date, '%G') // Full ISO-8601 year '2004' > Ox.formatDate(Ox.test.date, '%g') // Abbreviated ISO-8601 year '04' > Ox.formatDate(Ox.test.date, '%H') // Zero-padded hour (24-hour clock) '00' > Ox.formatDate(Ox.test.date, '%h') // Abbreviated month 'Jan' > Ox.formatDate(Ox.test.date, '%I') // Zero-padded hour (12-hour clock) '12' > Ox.formatDate(Ox.test.date, '%j') // Zero-padded day of the year '002' > Ox.formatDate(Ox.test.date, '%k') // Space-padded hour (24-hour clock) ' 0' > Ox.formatDate(Ox.test.date, '%l') // Space-padded hour (12-hour clock) '12' > Ox.formatDate(Ox.test.date, '%M') // Zero-padded minute '03' > Ox.formatDate(Ox.test.date, '%m') // Zero-padded month '01' > Ox.formatDate(Ox.test.date, '%n') // Newline '\n' > Ox.formatDate(Ox.test.date, '%p') // AM or PM 'AM' > Ox.formatDate(Ox.test.date, '%Q') // Quarter of the year '1' > Ox.formatDate(Ox.test.date, '%R') // Zero-padded hour and minute '00:03' > Ox.formatDate(Ox.test.date, '%r') // US time '12:03:04 AM' > Ox.formatDate(Ox.test.date, '%S') // Zero-padded second '04' > Ox.formatDate(Ox.test.date, '%s', true) // Number of seconds since the Epoch '1104620584' > Ox.formatDate(Ox.test.date, '%T') // Time '00:03:04' > Ox.formatDate(Ox.test.date, '%t') // Tab '\t' > Ox.formatDate(Ox.test.date, '%U') // Zero-padded week of the year (00-53, Sunday as first day) '01' > Ox.formatDate(Ox.test.date, '%u') // Decimal weekday (1-7, Monday as first day) '7' > Ox.formatDate(Ox.test.date, '%V') // Zero-padded ISO-8601 week of the year '53' > Ox.formatDate(Ox.test.date, '%v') // Formatted date ' 2-Jan-2005' > Ox.formatDate(Ox.test.date, '%W') // Zero-padded week of the year (00-53, Monday as first day) '00' > Ox.formatDate(Ox.test.date, '%w') // Decimal weekday (0-6, Sunday as first day) '0' > Ox.formatDate(Ox.test.date, '%X') // Full year with BC or AD '2005 AD' > Ox.formatDate(Ox.test.date, '%x') // Full year with BC or AD if year < 1000 '2005' > Ox.formatDate(Ox.test.date, '%Y') // Full year '2005' > Ox.formatDate(Ox.test.date, '%y') // Abbreviated year '05' > Ox.formatDate(Ox.test.date, '%Z', true) // Time zone name 'UTC' > Ox.formatDate(Ox.test.date, '%z', true) // Time zone offset '+0000' > Ox.formatDate(Ox.test.date, '%+', true) // Formatted date and time 'Sun Jan 2 00:03:04 CET 2005' > Ox.formatDate(Ox.test.date, '%%') '%' @*/ Ox.formatDate = function(date, str, utc) { // fixme: date and utc are optional, date can be date, number or string if (date === '') { return ''; } date = Ox.makeDate(date); var format = [ ['%', function() {return '%{%}';}], ['c', function() {return '%D %r';}], ['D', function() {return '%m/%d/%y';}], ['F', function() {return '%Y-%m-%d';}], ['h', function() {return '%b';}], ['R', function() {return '%H:%M';}], ['r', function() {return '%I:%M:%S %p';}], ['T', function() {return '%H:%M:%S';}], ['v', function() {return '%e-%b-%Y';}], ['\\+', function() {return '%a %b %e %H:%M:%S %Z %Y';}], ['A', function(d) {return Ox.WEEKDAYS[(Ox.getDay(d, utc) + 6) % 7];}], ['a', function(d) {return Ox.SHORT_WEEKDAYS[(Ox.getDay(d, utc) + 6) % 7];}], ['B', function(d) {return Ox.MONTHS[Ox.getMonth(d, utc)];}], ['b', function(d) {return Ox.SHORT_MONTHS[Ox.getMonth(d, utc)];}], ['C', function(d) {return Math.floor(Ox.getFullYear(d, utc) / 100).toString();}], ['d', function(d) {return Ox.pad(Ox.getDate(d, utc), 2);}], ['e', function(d) {return Ox.pad(Ox.getDate(d, utc), 2, ' ');}], ['G', function(d) {return Ox.getISOYear(d, utc);}], ['g', function(d) {return Ox.getISOYear(d, utc).toString().substr(-2);}], ['H', function(d) {return Ox.pad(Ox.getHours(d, utc), 2);}], ['I', function(d) {return Ox.pad((Ox.getHours(d, utc) + 11) % 12 + 1, 2);}], ['j', function(d) {return Ox.pad(Ox.getDayOfTheYear(d, utc), 3);}], ['k', function(d) {return Ox.pad(Ox.getHours(d, utc), 2, ' ');}], ['l', function(d) {return Ox.pad(((Ox.getHours(d, utc) + 11) % 12 + 1), 2, ' ');}], ['M', function(d) {return Ox.pad(Ox.getMinutes(d, utc), 2);}], ['m', function(d) {return Ox.pad((Ox.getMonth(d, utc) + 1), 2);}], ['p', function(d) {return Ox.AMPM[Math.floor(Ox.getHours(d, utc) / 12)];}], ['Q', function(d) {return Math.floor(Ox.getMonth(d, utc) / 4) + 1;}], ['S', function(d) {return Ox.pad(Ox.getSeconds(d, utc), 2);}], ['s', function(d) {return Math.floor(d.getTime() / 1000);}], ['U', function(d) {return Ox.pad(Ox.getWeek(d, utc), 2);}], ['u', function(d) {return Ox.getISODay(d, utc);}], ['V', function(d) {return Ox.pad(Ox.getISOWeek(d, utc), 2);}], ['W', function(d) { return Ox.pad(Math.floor((Ox.getDayOfTheYear(d, utc) + (Ox.getFirstDayOfTheYear(d, utc) || 7) - 2) / 7), 2); }], ['w', function(d) {return Ox.getDay(d, utc);}], ['X', function(d) { var y = Ox.getFullYear(d, utc); return Math.abs(y) + ' ' + Ox.BCAD[y < 0 ? 0 : 1]; }], ['x', function(d) { var y = Ox.getFullYear(d, utc); return Math.abs(y) + (y < 1000 ? ' ' + Ox.BCAD[y < 0 ? 0 : 1] : ''); }], ['Y', function(d) {return Ox.getFullYear(d, utc);}], ['y', function(d) {return Ox.getFullYear(d, utc).toString().substr(-2);}], ['Z', function(d) {return d.toString().split('(')[1].replace(')', '');}], ['z', function(d) {return Ox.getTimezoneOffsetString(d);}], ['n', function() {return '\n';}], ['t', function() {return '\t';}], ['\\{%\\}', function() {return '%';}] ]; format.forEach(function(v) { var regexp = new RegExp('%' + v[0], 'g'); if (regexp.test(str)) { str = str.replace(regexp, v[1](date)); } }); return str; }; /*@ Ox.formatDateRange Formats a date range as a string A date range is a pair of arbitrary-presicion date strings > Ox.formatDateRange('2000', '2001') '2000' > Ox.formatDateRange('2000', '2002') '2000 - 2002' > Ox.formatDateRange('2000-01', '2000-02') 'January 2000' > Ox.formatDateRange('2000-01', '2000-03') 'January - March 2000' > Ox.formatDateRange('2000-01-01', '2000-01-02') 'Sat, Jan 1, 2000' > Ox.formatDateRange('2000-01-01', '2000-01-03') 'Sat, Jan 1 - Mon, Jan 3, 2000' > Ox.formatDateRange('2000-01-01 00', '2000-01-01 01') 'Sat, Jan 1, 2000, 00:00' > Ox.formatDateRange('2000-01-01 00', '2000-01-01 02') 'Sat, Jan 1, 2000, 00:00 - 02:00' > Ox.formatDateRange('2000-01-01 00:00', '2000-01-01 00:01') 'Sat, Jan 1, 2000, 00:00' > Ox.formatDateRange('2000-01-01 00:00', '2000-01-01 00:02') 'Sat, Jan 1, 2000, 00:00 - 00:02' > Ox.formatDateRange('2000-01-01 00:00:00', '2000-01-01 00:00:01') 'Sat, Jan 1, 2000, 00:00:00' > Ox.formatDateRange('2000-01-01 00:00:00', '2000-01-01 00:00:02') 'Sat, Jan 1, 2000, 00:00:00 - 00:00:02' > Ox.formatDateRange('1999-12', '2000-01') 'December 1999' > Ox.formatDateRange('1999-12-31', '2000-01-01') 'Fri, Dec 31, 1999' > Ox.formatDateRange('1999-12-31 23:00', '2000-01-01 00:00') 'Fri, Dec 31, 1999, 23:00' > Ox.formatDateRange('-50', '50') '50 BC - 50 AD' > Ox.formatDateRange('-50-01-01', '-50-12-31') 'Sun, Jan 1 - Sun, Dec 31, 50 BC' > Ox.formatDateRange('-50-01-01 00:00:00', '-50-01-01 23:59:59') 'Sun, Jan 1, 50 BC, 00:00:00 - 23:59:59' @*/ Ox.formatDateRange = function(start, end, utc) { end = end || Ox.formatDate(new Date(), '%Y-%m-%d'); var isOneUnit = false, range = [start, end], strings, dates = range.map(function(str){ return Ox.parseDate(str, utc); }), parts = range.map(function(str) { var parts = Ox.compact( /(-?\d+)-?(\d+)?-?(\d+)? ?(\d+)?:?(\d+)?:?(\d+)?/.exec(str) ); parts.shift(); return parts.map(function(part) { return parseInt(part); }); }), precision = parts.map(function(parts) { return parts.length; }), y = parts[0][0] < 0 ? '%X' : '%Y', formats = [ y, '%B ' + y, '%a, %b %e, ' + y, '%a, %b %e, ' + y + ', %H:%M', '%a, %b %e, ' + y + ', %H:%M', '%a, %b %e, ' + y + ', %H:%M:%S', ]; if (precision[0] == precision[1]) { isOneUnit = true; Ox.loop(precision[0], function(i) { if (i < precision[0] - 1 && parts[0][i] != parts[1][i]) { isOneUnit = false; } if (i == precision[0] - 1 && parts[0][i] != parts[1][i] - 1) { isOneUnit = false; } return isOneUnit; }); } if (isOneUnit) { strings = [Ox.formatDate(dates[0], formats[precision[0] - 1], utc)]; } else { strings = [ Ox.formatDate(dates[0], formats[precision[0] - 1], utc), Ox.formatDate(dates[1], formats[precision[1] - 1], utc) ]; // if same year, and neither date is more precise than day, // then omit first year if ( parts[0][0] == parts[1][0] && precision[0] <= 3 && precision[1] <= 3 ) { strings[0] = Ox.formatDate( dates[0], formats[precision[0] - 1].replace( new RegExp(',? ' + y), '' ), utc ); } // if same day then omit second day if ( parts[0][0] == parts[1][0] && parts[0][1] == parts[1][1] && parts[0][2] == parts[1][2] ) { strings[1] = strings[1].split(', ').pop(); } } return strings.map(function(string) { // %e is a space-padded day return string.replace(' ', ' '); }).join(' - '); }; /*@ Ox.formatDateRangeDuration Formats the duration of a date range as a string A date range is a pair of arbitrary-presicion date strings > Ox.formatDateRangeDuration('2000-01-01 00:00:00', '2001-03-04 04:05:06') '1 year 2 months 3 days 4 hours 5 minutes 6 seconds' > Ox.formatDateRangeDuration('2000', '2001-01-01 00:00:01') '1 year 1 second' > Ox.formatDateRangeDuration('1999', '2000', true) '1 year' > Ox.formatDateRangeDuration('2000', '2001', true) '1 year' > Ox.formatDateRangeDuration('1999-02', '1999-03', true) '1 month' > Ox.formatDateRangeDuration('2000-02', '2000-03', true) '1 month' @*/ Ox.formatDateRangeDuration = function(start, end, utc) { end = end || Ox.formatDate(new Date(), '%Y-%m-%d'); var date = Ox.parseDate(start, utc), dates = [start, end].map(function(str) { return Ox.parseDate(str, utc); }), keys = ['year', 'month', 'day', 'hour', 'minute', 'second'], parts = ['FullYear', 'Month', 'Date', 'Hours', 'Minutes', 'Seconds'], values = []; Ox.forEach(keys, function(key, i) { while (true) { if (key == 'month') { // set the day to the same day in the next month, // or to its last day if the next month is shorter var day = Ox.getDate(date, utc); Ox.setDate(date, Math.min( day, Ox.getDaysInMonth( Ox.getFullYear(date, utc), Ox.getMonth(date, utc) + 2, utc ) ), utc); } // advance the date by one unit Ox['set' + parts[i]](date, Ox['get' + parts[i]](date, utc) + 1, utc); if (date <= dates[1]) { // still within the range, add one unit values[i] = (values[i] || 0) + 1; } else { // outside the range, rewind the date by one unit Ox['set' + parts[i]](date, Ox['get' + parts[i]](date, utc) - 1, utc); // and revert to original day key == 'month' && Ox.setDate(date, day, utc); break; } } }); return Ox.map(values, function(value, i) { return value ? value + ' ' + keys[i] + (value > 1 ? 's' : '') : null; }).join(' '); }; /*@ Ox.formatDuration Formats a duration as a string > Ox.formatDuration(3599.999) '01:00:00' > Ox.formatDuration(3599.999, 2) '01:00:00.00' > Ox.formatDuration(3599.999, 3) '00:59:59.999' > Ox.formatDuration(3599.999, 'short') '1h' > Ox.formatDuration(3599.999, 3, 'short') '59m 59.999s' > Ox.formatDuration(3599.999, 'long') '1 hour' > Ox.formatDuration(3599.999, 3, 'long') '59 minutes 59.999 seconds' > Ox.formatDuration(86520, 2) '1:00:02:00.00' > Ox.formatDuration(86520, 'long') '1 day 2 minutes' > Ox.formatDuration(31543203, 2) '1:000:02:00:03.00' > Ox.formatDuration(31543203, 'long') '1 year 2 hours 3 seconds' > Ox.formatDuration(0, 2) '00:00:00.00' > Ox.formatDuration(0, 'long') '' @*/ Ox.formatDuration = function(/*sec, dec, format*/) { var format = Ox.isString(arguments[arguments.length - 1]) ? arguments[arguments.length - 1] : '', dec = Ox.isNumber(arguments[1]) ? arguments[1] : 0, sec = Ox.round(arguments[0], dec), val = [ Math.floor(sec / 31536000), Math.floor(sec % 31536000 / 86400), Math.floor(sec % 86400 / 3600), Math.floor(sec % 3600 / 60), Ox.formatNumber(sec % 60, dec) ], str = !format ? [] : format == 'short' ? ['y', 'd', 'h', 'm', 's'] : ['year', 'day', 'hour', 'minute', 'second'], pad = [ val[0].toString().length, val[0] ? 3 : 1, 2, 2, dec ? dec + 3 : 2 ]; while (!val[0] && val.length > (!format ? 3 : 1)) { val.shift(); str.shift(); pad.shift(); } return Ox.map(val, function(v, i) { var ret; if (!format) { ret = Ox.pad(v, pad[i]); } else if (Ox.isNumber(v) ? v : parseFloat(v)) { ret = v + (format == 'long' ? ' ' : '') + str[i] + (format == 'long' && v != 1 ? 's' : ''); } else { ret = null; } return ret; }).join(!format ? ':' : ' '); }; /*@ Ox.formatNumber Formats a number with thousands separators > Ox.formatNumber(123456789, 3) "123,456,789.000" > Ox.formatNumber(-2000000 / 3, 3) "-666,666.667" > Ox.formatNumber(666666.666, 0) "666,667" @*/ Ox.formatNumber = function(num, dec) { // fixme: specify decimal and thousands separators var arr = [], abs = Math.abs(num), str = Ox.isUndefined(dec) ? abs.toString() : abs.toFixed(dec), spl = str.split('.'); while (spl[0]) { arr.unshift(spl[0].substr(-3)); spl[0] = spl[0].substr(0, spl[0].length - 3); } spl[0] = arr.join(','); return (num < 0 ? '-' : '') + spl.join('.'); }; /*@ Ox.formatOrdinal Formats a number as an ordinal > Ox.formatOrdinal(1) "1st" > Ox.formatOrdinal(2) "2nd" > Ox.formatOrdinal(3) "3rd" > Ox.formatOrdinal(4) "4th" > Ox.formatOrdinal(11) "11th" > Ox.formatOrdinal(12) "12th" > Ox.formatOrdinal(13) "13th" @*/ Ox.formatOrdinal = function(num) { var str = num.toString(), end = str[str.length - 1], ten = str.length > 1 && str[str.length - 2] == '1'; if (end == '1' && !ten) { str += 'st'; } else if (end == '2' && !ten) { str += 'nd'; } else if (end == '3' && !ten) { str += 'rd'; } else { str += 'th'; } return str; }; /*@ Ox.formatPercent Formats the relation of two numbers as a percentage > Ox.formatPercent(1, 1000, 2) "0.10%" @*/ Ox.formatPercent = function(num, total, dec) { return Ox.formatNumber(num / total * 100, dec) + '%' }; /*@ Ox.formatResolution Formats two values as a resolution > Ox.formatResolution([1920, 1080], 'px') "1920 x 1080 px" @*/ // fixme: should be formatDimensions Ox.formatResolution = function(arr, str) { return arr[0] + ' x ' + arr[1] + (str ? ' ' + str : ''); } /*@ Ox.formatString Basic string formatting > Ox.formatString('{0}{1}', ['foo', 'bar']) 'foobar' > Ox.formatString('{a}{b}', {a: 'foo', b: 'bar'}) 'foobar' @*/ Ox.formatString = function (str, obj) { return str.replace(/\{([^}]+)\}/g, function(str, match) { return obj[match]; }); }; /*@ Ox.formatValue Formats a numerical value > Ox.formatValue(0, "B") "0 KB" > Ox.formatValue(123456789, "B") "123.5 MB" > Ox.formatValue(1234567890, "B", true) "1.15 GiB" @*/ // fixme: is this the best name? Ox.formatValue = function(num, str, bin) { var base = bin ? 1024 : 1000, len = Ox.PREFIXES.length, val; Ox.forEach(Ox.PREFIXES, function(chr, i) { if (num < Math.pow(base, i + 2) || i == len - 1) { val = Ox.formatNumber(num / Math.pow(base, i + 1), i) + ' ' + chr + (bin ? 'i' : '') + str; return false; } }); return val; }; /*@ Ox.formatUnit Formats a number with a unit @*/ Ox.formatUnit = function(num, str, dec, factor) { dec = Ox.isUndefined(dec) ? 3 : dec; factor = Ox.isUndefined(factor) ? 1 : factor; return Ox.formatNumber(num * factor, dec) + (str == '%' ? '' : ' ') + str; }; /*@ Ox.parseDuration Takes a formatted duration, returns seconds > Ox.parseDuration('01:02:03') 3723 > Ox.parseDuration('3') 3 > Ox.parseDuration('2:') 120 > Ox.parseDuration('1::') 3600 @*/ Ox.parseDuration = function(str) { var split = str.split(':').reverse(); while (split.length > 3) { split.pop(); } return split.reduce(function(prev, curr, i) { return prev + (parseFloat(curr) || 0) * Math.pow(60, i); }, 0); }