dateable
Version:
A date formatter and parser for node
348 lines (290 loc) • 6.47 kB
JavaScript
/**
* Set default language
*/
var lang = require('./lang/en-us');
/**
* Format date
*
* @param {Date} date
* @param {String} format
* @return {String}
* @api public
*/
function dateable (date, format) {
var date = toObject(date);
var re = /Y{2,4}|[Md]{1,4}|[DHhms]{1,2}|[Aa]|"[^"]*"|'[^']*'/g;
format = formats[format] || format;
return format.replace(re, function (part) {
switch (part) {
case 'YYYY':
return pad(date.Y, 3);
case 'YY':
return ('' + date.Y).slice(-2);
case 'MMMM':
case 'MMM':
return lang[part][date.M];
case 'MM':
return pad(date.M + 1);
case 'M':
return date.M + 1;
case 'DD':
return pad(date.D);
case 'D':
return date.D;
case 'dddd':
case 'ddd':
return lang[part][date.d];
case 'A':
return date.A;
case 'a':
return date.a
case 'H':
return date.H
case 'HH':
return pad(date.H);
case 'hh':
return pad(date.h);
case 'h':
return date.h
case 'mm':
return pad(date.m);
case 'm':
return date.m
case 'ss':
return pad(date.s);
case 's':
return date.s;
default:
return part.slice(1, -1);
}
});
};
/**
* Alias formatter
*/
dateable.format = dateable;
/**
* Units
*/
var units = dateable.units = {
years : 31536000000
, months : 2592000000
, weeks : 604800000
, days : 86400000
, hours : 3600000
, minutes : 60000
, seconds : 1000
};
/**
* Stored formats
*/
var formats = dateable.formats = {};
/**
* Set language
*
* @param {String|Object} name
* @api public
*/
dateable.language = function (name) {
// object
if (typeof name != 'string') {
lang = name;
return;
}
// string
try {
lang = require('./lang/' + name);
} catch (e) {
throw new Error('language does not exist');
}
};
/**
* Parse a date string
*
* @param {String} string
* @param {String} format
* @return {Date}
* @api public
*/
dateable.parse = function (string, format) {
var re = /Y{2,4}|[Md]{1,4}|[DHhms]{1,2}|[Aa]/g;
var offset = 0;
var parts = {};
var token;
format = formats[format] || format;
// Strip the string from the escaped parts of the format
format = format.replace(/"[^"]*"|'[^']*'/g, function (str) {
string = string.replace(str.slice(1, -1), '');
return '';
});
var stringLength = string.length;
while (token = re.exec(format)) {
var index = token.index + offset
var tokenLength = token[0].length
var part = string.substr(index, tokenLength);
index += tokenLength - 1;
// Remove characters that are not part of the format
// e.g, MMMM > May
part = part.replace(/\W+.*/, function (str) {
index -= str.length;
return '';
});
// Looks ahead for characters beyond the
// specified format, e.g, D > 9
while (++index < stringLength) {
if (!(/\d|\w/).test(string[index])) break;
part += string[index];
}
offset += part.length - tokenLength;
if (/[Md]{3,4}/.test(token[0])) {
part = lang[token[0]].indexOf(part);
} else if (token[0][0] === 'M') {
part--;
}
parts[token[0][0]] = part;
}
return toDate(parts);
};
/**
* Relative time
*
* @param {Date} date
* @param {String} [unit]
* @return {String}
* @api public
*/
dateable.when = function (date, unit) {
var now = new Date();
var diff = date.valueOf() - now.valueOf();
var time = 'present';
unit = unit || determineUnit(diff);
diff = Math.round(diff / units[unit])
if (diff !== 0) {
time = diff < 0 ? 'past' : 'future';
}
diff = Math.abs(diff);
return format(lang.time[time], pluralize(diff, unit));
};
/**
* Return difference between two dates
*
* @param {Date} start
* @param {Date} end
* @param {String} [unit]
* @return {String}
* @api public
*/
dateable.diff = function (start, end, unit) {
var diff = start.valueOf() - end.valueOf()
var unit = unit || determineUnit(diff)
diff = Math.abs(Math.round(diff / units[unit]))
return pluralize(diff, unit);
};
/**
* Pluralize unit
*
* @param {Number} value
* @param {String} unit
* @return {String}
* @api private
*/
function pluralize (value, unit) {
var form = lang.units[unit][value > 1 ? 1 : 0];
return format(form, value);
};
/**
* Find the best matching unit for an amount of time (ms)
*
* @param {Number} ms
* @return {String}
* @api private
*/
function determineUnit (ms) {
ms = Math.abs(ms);
for (var unit in units) {
if (ms > units[unit]) break;
}
return unit;
}
/**
* Pad a number with leading zeros
*
* @param {Number} number
* @param {Number} length
* @return {String}
* @api private
*/
function pad (number, length) {
return number < Math.pow(10, length || 1)
? '0' + number
: '' + number;
}
/**
* Format string
*
* @param {String} str
* @return {String}
* @api private
*/
function format (str) {
var args = Array.prototype.slice.call(arguments, 1);
return str.replace(/(%[djs])/g, function (match) {
return args.length ? args.shift() : match;
});
}
/**
* Convert an object to a date
*
* @param {Object} object
* @return {Number} length
* @api private
*/
function toDate (obj) {
var date = new Date(0);
var abbr = obj.a || obj.A;
// Handle AM/PM
if (!obj.h && obj.H && abbr) {
abbr = abbr.toLowerCase();
if (abbr == 'pm' && obj.H < 12) {
obj.h = obj.H + 12;
}
}
// Handle years
if (obj.Y && obj.Y.length == 2) {
if (parseInt(obj.Y, 10) > 50) {
obj.Y = '19' + obj.Y;
} else {
obj.Y = '20' + obj.Y;
}
}
date.setFullYear(obj.Y || 0);
date.setMonth(obj.M || 0);
date.setDate(obj.D || 0);
date.setHours(obj.h || 0);
date.setMinutes(obj.m || 0);
date.setSeconds(obj.s || 0);
return date;
}
/**
* Convert a date to an object
*
* @param {Object} date
* @return {Object}
* @api private
*/
function toObject (date) {
var obj = {
Y: date.getFullYear(),
M: date.getMonth(),
D: date.getDate(),
d: date.getDay(),
h: date.getHours(),
m: date.getMinutes(),
s: date.getSeconds()
};
obj.H = obj.h - (obj.h > 12 ? 12 : 0);
obj.a = obj.h >= 12 ? 'pm' : 'am';
obj.A = obj.a.toUpperCase();
return obj;
}
module.exports = dateable;