-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
324 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
exports.strftime = strftime = require('./strftime'); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,322 @@ | ||
/** | ||
* strftime formatters for javascript based on the Open Group specification defined at | ||
* http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html | ||
* This implementation does not include modified conversion specifiers (i.e., Ex and Ox) | ||
*/ | ||
|
||
/** | ||
* Pad a number with leading spaces, zeroes or something else | ||
* @method xPad | ||
* @param x {Number} The number to be padded | ||
* @param pad {String} The character to pad the number with | ||
* @param r {Number} (optional) The base of the pad, eg, 10 implies to two digits, 100 implies to 3 digits. | ||
* @private | ||
*/ | ||
function xPad(x, pad, r) | ||
{ | ||
if(typeof r === "undefined") { | ||
r=10; | ||
} | ||
pad = pad.toString(); | ||
for( ; parseInt(x, 10)<r && r>1; r/=10) { | ||
x = pad + x; | ||
} | ||
return x.toString(); | ||
} | ||
|
||
var formats = { | ||
a: function (d, l) { return l.a[d.getDay()]; }, | ||
A: function (d, l) { return l.A[d.getDay()]; }, | ||
b: function (d, l) { return l.b[d.getMonth()]; }, | ||
B: function (d, l) { return l.B[d.getMonth()]; }, | ||
C: function (d) { return xPad(parseInt(d.getFullYear()/100, 10), 0); }, | ||
d: ["getDate", "0"], | ||
e: ["getDate", " "], | ||
g: function (d) { return xPad(parseInt(formats.G(d)%100, 10), 0); }, | ||
G: function (d) { | ||
var y = d.getFullYear(); | ||
var V = parseInt(formats.V(d), 10); | ||
var W = parseInt(formats.W(d), 10); | ||
|
||
if(W > V) { | ||
y++; | ||
} else if(W===0 && V>=52) { | ||
y--; | ||
} | ||
|
||
return y; | ||
}, | ||
H: ["getHours", "0"], | ||
I: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, 0); }, | ||
j: function (d) { | ||
var gmd_1 = new Date("" + d.getFullYear() + "/1/1 GMT"); | ||
var gmdate = new Date("" + d.getFullYear() + "/" + (d.getMonth()+1) + "/" + d.getDate() + " GMT"); | ||
var ms = gmdate - gmd_1; | ||
var doy = parseInt(ms/60000/60/24, 10)+1; | ||
return xPad(doy, 0, 100); | ||
}, | ||
k: ["getHours", " "], | ||
l: function (d) { var I=d.getHours()%12; return xPad(I===0?12:I, " "); }, | ||
m: function (d) { return xPad(d.getMonth()+1, 0); }, | ||
M: ["getMinutes", "0"], | ||
p: function (d, l) { return l.p[d.getHours() >= 12 ? 1 : 0 ]; }, | ||
P: function (d, l) { return l.P[d.getHours() >= 12 ? 1 : 0 ]; }, | ||
s: function (d, l) { return parseInt(d.getTime()/1000, 10); }, | ||
S: ["getSeconds", "0"], | ||
u: function (d) { var dow = d.getDay(); return dow===0?7:dow; }, | ||
U: function (d) { | ||
var doy = parseInt(formats.j(d), 10); | ||
var rdow = 6-d.getDay(); | ||
var woy = parseInt((doy+rdow)/7, 10); | ||
return xPad(woy, 0); | ||
}, | ||
V: function (d) { | ||
var woy = parseInt(formats.W(d), 10); | ||
var dow1_1 = (new Date("" + d.getFullYear() + "/1/1")).getDay(); | ||
// First week is 01 and not 00 as in the case of %U and %W, | ||
// so we add 1 to the final result except if day 1 of the year | ||
// is a Monday (then %W returns 01). | ||
// We also need to subtract 1 if the day 1 of the year is | ||
// Friday-Sunday, so the resulting equation becomes: | ||
var idow = woy + (dow1_1 > 4 || dow1_1 <= 1 ? 0 : 1); | ||
if(idow === 53 && (new Date("" + d.getFullYear() + "/12/31")).getDay() < 4) | ||
{ | ||
idow = 1; | ||
} | ||
else if(idow === 0) | ||
{ | ||
idow = formats.V(new Date("" + (d.getFullYear()-1) + "/12/31")); | ||
} | ||
|
||
return xPad(idow, 0); | ||
}, | ||
w: "getDay", | ||
W: function (d) { | ||
var doy = parseInt(formats.j(d), 10); | ||
var rdow = 7-formats.u(d); | ||
var woy = parseInt((doy+rdow)/7, 10); | ||
return xPad(woy, 0, 10); | ||
}, | ||
y: function (d) { return xPad(d.getFullYear()%100, 0); }, | ||
Y: "getFullYear", | ||
z: function (d) { | ||
var o = d.getTimezoneOffset(); | ||
var H = xPad(parseInt(Math.abs(o/60), 10), 0); | ||
var M = xPad(Math.abs(o%60), 0); | ||
return (o>0?"-":"+") + H + M; | ||
}, | ||
Z: function (d) { | ||
var tz = d.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/, "$2").replace(/[a-z ]/g, ""); | ||
if(tz.length > 4) { | ||
tz = formats.z(d); | ||
} | ||
return tz; | ||
}, | ||
"%": function (d) { return "%"; } | ||
}; | ||
|
||
var aggregates = { | ||
c: "locale", | ||
D: "%m/%d/%y", | ||
F: "%Y-%m-%d", | ||
h: "%b", | ||
n: "\n", | ||
r: "%I:%M:%S %p", | ||
R: "%H:%M", | ||
t: "\t", | ||
T: "%H:%M:%S", | ||
x: "locale", | ||
X: "locale" | ||
//"+": "%a %b %e %T %Z %Y" | ||
}; | ||
|
||
/** | ||
* Takes a native JavaScript Date and formats it as a string for display to user. | ||
* | ||
* @method strftime | ||
* @param oDate {Date} Date. | ||
* @param sFormat {Object} (Required) Format specifier | ||
* <p> | ||
* Any strftime string is supported, such as "%I:%M:%S %p". strftime has several format specifiers defined by the Open group at | ||
* <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html">http://www.opengroup.org/onlinepubs/007908799/xsh/strftime.html</a> | ||
* PHP added a few of its own, defined at <a href="http://www.php.net/strftime">http://www.php.net/strftime</a> | ||
* </p> | ||
* <p> | ||
* This javascript implementation supports all the PHP specifiers and a few more. The full list is below. | ||
* </p> | ||
* <dl> | ||
* <dt>%a</dt> <dd>abbreviated weekday name according to the current locale</dd> | ||
* <dt>%A</dt> <dd>full weekday name according to the current locale</dd> | ||
* <dt>%b</dt> <dd>abbreviated month name according to the current locale</dd> | ||
* <dt>%B</dt> <dd>full month name according to the current locale</dd> | ||
* <dt>%c</dt> <dd>preferred date and time representation for the current locale</dd> | ||
* <dt>%C</dt> <dd>century number (the year divided by 100 and truncated to an integer, range 00 to 99)</dd> | ||
* <dt>%d</dt> <dd>day of the month as a decimal number (range 01 to 31)</dd> | ||
* <dt>%D</dt> <dd>same as %m/%d/%y</dd> | ||
* <dt>%e</dt> <dd>day of the month as a decimal number, a single digit is preceded by a space (range " 1" to "31")</dd> | ||
* <dt>%F</dt> <dd>same as %Y-%m-%d (ISO 8601 date format)</dd> | ||
* <dt>%g</dt> <dd>like %G, but without the century</dd> | ||
* <dt>%G</dt> <dd>The 4-digit year corresponding to the ISO week number</dd> | ||
* <dt>%h</dt> <dd>same as %b</dd> | ||
* <dt>%H</dt> <dd>hour as a decimal number using a 24-hour clock (range 00 to 23)</dd> | ||
* <dt>%I</dt> <dd>hour as a decimal number using a 12-hour clock (range 01 to 12)</dd> | ||
* <dt>%j</dt> <dd>day of the year as a decimal number (range 001 to 366)</dd> | ||
* <dt>%k</dt> <dd>hour as a decimal number using a 24-hour clock (range 0 to 23); single digits are preceded by a blank. (See also %H.)</dd> | ||
* <dt>%l</dt> <dd>hour as a decimal number using a 12-hour clock (range 1 to 12); single digits are preceded by a blank. (See also %I.) </dd> | ||
* <dt>%m</dt> <dd>month as a decimal number (range 01 to 12)</dd> | ||
* <dt>%M</dt> <dd>minute as a decimal number</dd> | ||
* <dt>%n</dt> <dd>newline character</dd> | ||
* <dt>%p</dt> <dd>either "AM" or "PM" according to the given time value, or the corresponding strings for the current locale</dd> | ||
* <dt>%P</dt> <dd>like %p, but lower case</dd> | ||
* <dt>%r</dt> <dd>time in a.m. and p.m. notation equal to %I:%M:%S %p</dd> | ||
* <dt>%R</dt> <dd>time in 24 hour notation equal to %H:%M</dd> | ||
* <dt>%s</dt> <dd>number of seconds since the Epoch, ie, since 1970-01-01 00:00:00 UTC</dd> | ||
* <dt>%S</dt> <dd>second as a decimal number</dd> | ||
* <dt>%t</dt> <dd>tab character</dd> | ||
* <dt>%T</dt> <dd>current time, equal to %H:%M:%S</dd> | ||
* <dt>%u</dt> <dd>weekday as a decimal number [1,7], with 1 representing Monday</dd> | ||
* <dt>%U</dt> <dd>week number of the current year as a decimal number, starting with the | ||
* first Sunday as the first day of the first week</dd> | ||
* <dt>%V</dt> <dd>The ISO 8601:1988 week number of the current year as a decimal number, | ||
* range 01 to 53, where week 1 is the first week that has at least 4 days | ||
* in the current year, and with Monday as the first day of the week.</dd> | ||
* <dt>%w</dt> <dd>day of the week as a decimal, Sunday being 0</dd> | ||
* <dt>%W</dt> <dd>week number of the current year as a decimal number, starting with the | ||
* first Monday as the first day of the first week</dd> | ||
* <dt>%x</dt> <dd>preferred date representation for the current locale without the time</dd> | ||
* <dt>%X</dt> <dd>preferred time representation for the current locale without the date</dd> | ||
* <dt>%y</dt> <dd>year as a decimal number without a century (range 00 to 99)</dd> | ||
* <dt>%Y</dt> <dd>year as a decimal number including the century</dd> | ||
* <dt>%z</dt> <dd>numerical time zone representation</dd> | ||
* <dt>%Z</dt> <dd>time zone name or abbreviation</dd> | ||
* <dt>%%</dt> <dd>a literal "%" character</dd> | ||
* </dl> | ||
* @param sLocale {String} (Optional)</dt> | ||
* The locale to use when displaying days of week, months of the year, and other locale specific | ||
* strings. If not specified, this defaults to "en" (though this may be overridden by the deprecated Y.config.locale). | ||
* The following locales are built in: | ||
* <dl> | ||
* <dt>en</dt> | ||
* <dd>English</dd> | ||
* <dt>en-US</dt> | ||
* <dd>US English</dd> | ||
* <dt>en-GB</dt> | ||
* <dd>British English</dd> | ||
* <dt>en-AU</dt> | ||
* <dd>Australian English (identical to British English)</dd> | ||
* </dl> | ||
* @return {String} Formatted date for display. | ||
*/ | ||
function strftime(oDate, sFormat, sLocale) { | ||
var locale; | ||
|
||
if(!oDate) { | ||
return ""; | ||
} | ||
|
||
if(!sFormat) { | ||
sFormat=""; | ||
} | ||
|
||
if(!sLocale) { | ||
sLocale = 'en-US'; | ||
} | ||
|
||
sLocale = sLocale.replace(/_/g, "-"); | ||
|
||
// Make sure we have a definition for the requested locale, or default to en. | ||
if(!LOCALES[sLocale]) { | ||
console.warn("selected locale " + sLocale + " not found, trying alternatives"); | ||
var tmpLocale = sLocale.replace(/-[a-zA-Z]+$/, ""); | ||
if(tmpLocale in LOCALES) { | ||
sLocale = tmpLocale; | ||
} else { | ||
sLocale = "en"; | ||
} | ||
console.info("falling back to " + sLocale); | ||
} | ||
|
||
locale = LOCALES[sLocale]; | ||
|
||
var replace_aggs = function (m0, m1) { | ||
var f = aggregates[m1]; | ||
return (f === "locale" ? locale[m1] : f); | ||
}; | ||
|
||
var replace_formats = function (m0, m1) { | ||
var f = formats[m1]; | ||
if(typeof f == 'string') // string => built in date function | ||
return oDate[f](); | ||
else if(typeof f == 'function') // function => our own function | ||
return f.call(oDate, oDate, locale); | ||
else if(f instanceof Array && typeof f[0] === 'string') // built in function with padding | ||
return xPad(oDate[f[0]](), f[1]); | ||
else { | ||
console.warn("unrecognised replacement type, please file a bug (format: " + sFormat + ")"); | ||
return m1; | ||
} | ||
}; | ||
|
||
// Preprocess by replacing %% with %<BS>, then later we'll replace %<BS> with %% | ||
// XXX this is a hack. There's a very low, but non-0 probability that an actual %<BS> | ||
// will show up in a string, and this will break when that happens. -- Philip 2011/01/10 | ||
sFormat = sFormat.replace(/%%/g, "%\b"); | ||
|
||
// First replace aggregates (run in a loop because an agg may be made up of other aggs) | ||
while(sFormat.match(/%[cDFhnrRtTxX]/)) { | ||
sFormat = sFormat.replace(/%([cDFhnrRtTxX])/g, replace_aggs); | ||
} | ||
|
||
// Now replace formats (do not run in a loop otherwise %%a will be replaced with the value of %a) | ||
var str = sFormat.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ])/g, replace_formats); | ||
|
||
// Post-process now to change %<BS> back to %% | ||
str = str.replace("%\b", '%%'); | ||
|
||
replace_aggs = replace_formats = undefined; | ||
|
||
return str; | ||
} | ||
|
||
var LOCALES = {}; | ||
|
||
LOCALES['en'] = { | ||
a: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], | ||
A: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], | ||
b: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], | ||
B: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], | ||
c: "%a %d %b %Y %T %Z", | ||
p: ["AM", "PM"], | ||
P: ["am", "pm"], | ||
r: "%I:%M:%S %p", | ||
x: "%d/%m/%y", | ||
X: "%T" | ||
}; | ||
|
||
function addLocale(sName, oLocale, sBase) { | ||
if(!sBase) | ||
sBase = 'en'; | ||
LOCALES[sName] = oLocale; | ||
|
||
for(k in LOCALES[sBase]) { | ||
if(!(k in oLocale)) { | ||
LOCALES[sName][k] = LOCALES[sBase][k]; | ||
} | ||
} | ||
} | ||
|
||
addLocale('en-US', { | ||
c: "%a %d %b %Y %I:%M:%S %p %Z", | ||
x: "%m/%d/%Y", | ||
X: "%I:%M:%S %p" | ||
}); | ||
|
||
addLocale('en-GB', { | ||
r: "%l:%M:%S %P %Z" | ||
}); | ||
|
||
addLocale('en-AU', {}); | ||
|
||
strftime.addLocale=addLocale; | ||
module.exports = strftime; | ||
|