diff --git a/strftime.js b/strftime.js index b594bf0..4cb902d 100644 --- a/strftime.js +++ b/strftime.js @@ -47,6 +47,12 @@ 'getSeconds' ]; + var _options; + var _locale; + var _date; + var _timeZone; + var _timestamp; + function strftime(fmt, d, locale) { return _strftime(fmt, d, locale); } @@ -85,252 +91,375 @@ // - locale [object] an object with the same structure as DefaultLocale // - timezone [number] timezone offset in minutes from GMT function _strftime(format, d, locale, options) { - var _options = options || {}; - var _locale = locale; - var _d = d; + _options = options || {}; + _locale = locale; + _date = d; + _timeZone = _options.timezone; // d and locale are optional so check if d is really the locale - if (_d && !quacksLikeDate(_d)) { - _locale = _d; - _d = undefined; + if (_date && !quacksLikeDate(_date)) { + _locale = _date; + _date = undefined; } - _d = _d || new Date(); + _date = _date || new Date(); + + // Hang on to this Unix timestamp because we might mess with it directly below. + _timestamp = _date.getTime(); _locale = _locale || DefaultLocale; _locale.formats = _locale.formats || {}; - // Hang on to this Unix timestamp because we might mess with it directly below. - var timestamp = _d.getTime(); - var tz = _options.timezone; - var tzType = typeof tz; + var tzType = typeof _timeZone; if (_options.utc || tzType === 'number' || tzType === 'string') { - _d = dateToUTC(_d); + _date = dateToUTC(_date); } - if (tz) { + if (_timeZone) { // ISO 8601 format timezone string, [-+]HHMM // Convert to the number of minutes and it'll be applied to the date below. if (tzType === 'string') { - var sign = tz[0] === '-' ? -1 : 1; - var hours = parseInt(tz.slice(1, 3), 10); - var mins = parseInt(tz.slice(3, 5), 10); + var sign = _timeZone[0] === '-' ? -1 : 1; + var hours = parseInt(_timeZone.slice(1, 3), 10); + var mins = parseInt(_timeZone.slice(3, 5), 10); - tz = sign * (60 * hours) + mins; + _timeZone = sign * (60 * hours) + mins; } - _d = new Date(_d.getTime() + (tz * 60000)); + _date = new Date(_date.getTime() + (_timeZone * 60000)); } // Most of the specifiers supported by C's strftime, and some from Ruby. // Some other syntax extensions from Ruby are supported: %-, %_, and %0 // to pad with nothing, space, or zero (respectively). - return format.replace(/%([-_0]?.)/g, function (_, c) { - var padding; - var _c = c; + /*var part; + var regExp = /%([-_0]?.)/g;*/ - if (_c.length === 2) { - switch (_c[0]) { - // omit padding - case '-': - padding = ''; - break; + // while (part = regExp.exec(format)) { + // var padding; + // var _c = part[1]; + // + // if (_c.length === 2) { + // switch (_c[0]) { + // // omit padding + // case '-': + // padding = ''; + // break; + // + // // pad with space + // case '_': + // padding = ' '; + // break; + // + // // pad with zero + // case '0': + // padding = '0'; + // break; + // + // // unrecognized, return the format + // default: + // return part[0]; + // } + // + // _c = _c[1]; + // } + // + // switch (_c) { + // // Examples for new Date(0) in GMT + // + // // 'Thursday' + // case 'A': + // return _locale.days[_d.getDay()]; + // + // // 'Thu' + // case 'a': + // return _locale.shortDays[_d.getDay()]; + // + // // 'January' + // case 'B': + // return _locale.months[_d.getMonth()]; + // + // // 'Jan' + // case 'b': + // return _locale.shortMonths[_d.getMonth()]; + // + // // '19' + // case 'C': + // return pad(Math.floor(_d.getFullYear() / 100), padding); + // + // // '01/01/70' + // case 'D': + // return _strftime(_locale.formats.D || '%m/%d/%y', _d, _locale); + // + // // '01' + // case 'd': + // return pad(_d.getDate(), padding); + // + // // '01' + // case 'e': + // return _d.getDate(); + // + // // '1970-01-01' + // case 'F': + // return _strftime(_locale.formats.F || '%Y-%m-%d', _d, _locale); + // + // // '00' + // case 'H': + // return pad(_d.getHours(), padding); + // + // // 'Jan' + // case 'h': + // return _locale.shortMonths[_d.getMonth()]; + // + // // '12' + // case 'I': + // return pad(hours12(_d), padding); + // + // // '000' + // case 'j': + // var y = new Date(_d.getFullYear(), 0, 1); + // var day = Math.ceil((_d.getTime() - y.getTime()) / (1000 * 60 * 60 * 24)); + // return pad(day, null, 3); + // + // // ' 0' + // case 'k': + // return pad(_d.getHours(), padding == null ? ' ' : padding); + // + // // '000' + // case 'L': + // return pad(Math.floor(timestamp % 1000), null, 3); + // + // // '12' + // case 'l': + // return pad(hours12(_d), padding == null ? ' ' : padding); + // + // // '00' + // case 'M': + // return pad(_d.getMinutes(), padding); + // + // // '01' + // case 'm': + // return pad(_d.getMonth() + 1, padding); + // + // // '\n' + // case 'n': + // return '\n'; + // + // // '1st' + // case 'o': + // return String(_d.getDate()) + ordinal(_d.getDate()); + // + // // 'am' + // case 'P': + // return _d.getHours() < 12 ? _locale.am : _locale.pm; + // + // // 'AM' + // case 'p': + // return _d.getHours() < 12 ? _locale.AM : _locale.PM; + // + // // '00:00' + // case 'R': + // return _strftime(_locale.formats.R || '%H:%M', _d, _locale); + // + // // '12:00:00 AM' + // case 'r': + // return _strftime(_locale.formats.r || '%I:%M:%S %p', _d, _locale); + // + // // '00' + // case 'S': + // return pad(_d.getSeconds(), padding); + // + // // '0' + // case 's': + // return Math.floor(timestamp / 1000); + // + // // '00:00:00' + // case 'T': + // return _strftime(_locale.formats.T || '%H:%M:%S', _d, _locale); + // + // // '\t' + // case 't': + // return '\t'; + // + // // '00' + // case 'U': + // return pad(weekNumber(_d, 'sunday'), padding); + // + // // '4' + // case 'u': + // var day = _d.getDay(); + // return day === 0 ? 7 : day; // 1 - 7, Monday is first day of the week + // + // // '1-Jan-1970' + // case 'v': + // return _strftime(_locale.formats.v || '%e-%b-%Y', _d, _locale); + // + // // '00' + // case 'W': + // return pad(weekNumber(_d, 'monday'), padding); + // + // // '4' + // case 'w': + // return _d.getDay(); // 0 - 6, Sunday is first day of the week + // + // // '1970' + // case 'Y': + // return _d.getFullYear(); + // + // // '70' + // case 'y': + // return String(_d.getFullYear()).slice(-2); + // + // // 'GMT' + // case 'Z': + // if (_options.utc) { + // return 'GMT'; + // } else { + // var tzString = _d.toString().match(/\((\w+)\)/); + // return tzString && tzString[1] || ''; + // } + // + // // '+0000' + // case 'z': + // if (_options.utc) { + // return '+0000'; + // } else { + // var off = typeof tz === 'number' ? tz : -_d.getTimezoneOffset(); + // return (off < 0 ? '-' : '+') + pad(Math.abs(off / 60)) + pad(off % 60); + // } + // + // default: + // return _c; + // } + // } - // pad with space - case '_': - padding = ' '; - break; + return format.replace(/%([-_0]?.)/g, processing); + } - // pad with zero - case '0': - padding = '0'; - break; + function processing(_, c) { + var padding; + var _c = c; + var day; - // unrecognized, return the format - default: - return _; - } + if (_c.length === 2) { + switch (_c[0]) { + // omit padding + case '-': + padding = ''; + break; - _c = _c[1]; - } + // pad with space + case '_': + padding = ' '; + break; - switch (_c) { - // Examples for new Date(0) in GMT - - // 'Thursday' - case 'A': - return _locale.days[_d.getDay()]; - - // 'Thu' - case 'a': - return _locale.shortDays[_d.getDay()]; - - // 'January' - case 'B': - return _locale.months[_d.getMonth()]; - - // 'Jan' - case 'b': - return _locale.shortMonths[_d.getMonth()]; - - // '19' - case 'C': - return pad(Math.floor(_d.getFullYear() / 100), padding); - - // '01/01/70' - case 'D': - return _strftime(_locale.formats.D || '%m/%d/%y', _d, _locale); - - // '01' - case 'd': - return pad(_d.getDate(), padding); - - // '01' - case 'e': - return _d.getDate(); - - // '1970-01-01' - case 'F': - return _strftime(_locale.formats.F || '%Y-%m-%d', _d, _locale); - - // '00' - case 'H': - return pad(_d.getHours(), padding); - - // 'Jan' - case 'h': - return _locale.shortMonths[_d.getMonth()]; - - // '12' - case 'I': - return pad(hours12(_d), padding); - - // '000' - case 'j': - var y = new Date(_d.getFullYear(), 0, 1); - var day = Math.ceil((_d.getTime() - y.getTime()) / (1000 * 60 * 60 * 24)); - return pad(day, null, 3); - - // ' 0' - case 'k': - return pad(_d.getHours(), padding == null ? ' ' : padding); - - // '000' - case 'L': - return pad(Math.floor(timestamp % 1000), null, 3); - - // '12' - case 'l': - return pad(hours12(_d), padding == null ? ' ' : padding); - - // '00' - case 'M': - return pad(_d.getMinutes(), padding); - - // '01' - case 'm': - return pad(_d.getMonth() + 1, padding); - - // '\n' - case 'n': - return '\n'; - - // '1st' - case 'o': - return String(_d.getDate()) + ordinal(_d.getDate()); - - // 'am' - case 'P': - return _d.getHours() < 12 ? _locale.am : _locale.pm; - - // 'AM' - case 'p': - return _d.getHours() < 12 ? _locale.AM : _locale.PM; - - // '00:00' - case 'R': - return _strftime(_locale.formats.R || '%H:%M', _d, _locale); - - // '12:00:00 AM' - case 'r': - return _strftime(_locale.formats.r || '%I:%M:%S %p', _d, _locale); - - // '00' - case 'S': - return pad(_d.getSeconds(), padding); - - // '0' - case 's': - return Math.floor(timestamp / 1000); - - // '00:00:00' - case 'T': - return _strftime(_locale.formats.T || '%H:%M:%S', _d, _locale); - - // '\t' - case 't': - return '\t'; - - // '00' - case 'U': - return pad(weekNumber(_d, 'sunday'), padding); - - // '4' - case 'u': - var day = _d.getDay(); - return day === 0 ? 7 : day; // 1 - 7, Monday is first day of the week - - // '1-Jan-1970' - case 'v': - return _strftime(_locale.formats.v || '%e-%b-%Y', _d, _locale); - - // '00' - case 'W': - return pad(weekNumber(_d, 'monday'), padding); - - // '4' - case 'w': - return _d.getDay(); // 0 - 6, Sunday is first day of the week - - // '1970' - case 'Y': - return _d.getFullYear(); - - // '70' - case 'y': - return String(_d.getFullYear()).slice(-2); - - // 'GMT' - case 'Z': - if (_options.utc) { - return 'GMT'; - } else { - var tzString = _d.toString().match(/\((\w+)\)/); - return tzString && tzString[1] || ''; - } - - // '+0000' - case 'z': - if (_options.utc) { - return '+0000'; - } else { - var off = typeof tz === 'number' ? tz : -_d.getTimezoneOffset(); - return (off < 0 ? '-' : '+') + pad(Math.abs(off / 60)) + pad(off % 60); - } + // pad with zero + case '0': + padding = '0'; + break; + // unrecognized, return the format default: - return _c; + return _; } - }); - } - - function dateToUTC(d) { - var msDelta = (d.getTimezoneOffset() || 0) * 60000; - - return new Date(d.getTime() + msDelta); + + _c = _c[1]; + } + + if (_c === 'A') { + return _locale.days[_date.getDay()]; + } else if (_c === 'a') { + return _locale.shortDays[_date.getDay()]; + } else if (_c === 'B') { + return _locale.months[_date.getMonth()]; + } else if (_c === 'b') { + return _locale.shortMonths[_date.getMonth()]; + } else if (_c === 'C') { + return pad(Math.floor(_date.getFullYear() / 100), padding); + } else if (_c === 'D') { + return _strftime(_locale.formats.D || '%m/%d/%y', _date, _locale); + } else if (_c === 'd') { + return pad(_date.getDate(), padding); + } else if (_c === 'e') { + return _date.getDate(); + } else if (_c === 'F') { + return _strftime(_locale.formats.F || '%Y-%m-%d', _date, _locale); + } else if (_c === 'H') { + return pad(_date.getHours(), padding); + } else if (_c === 'h') { + return _locale.shortMonths[_date.getMonth()]; + } else if (_c === 'I') { + return pad(hours12(_date), padding); + } else if (_c === 'j') { + var y = new Date(_date.getFullYear(), 0, 1); + day = Math.ceil((_date.getTime() - y.getTime()) / (1000 * 60 * 60 * 24)); + return pad(day, null, 3); + } else if (_c === 'k') { + return pad(_date.getHours(), padding == null ? ' ' : padding); + } else if (_c === 'L') { + return pad(Math.floor(_timestamp % 1000), null, 3); + } else if (_c === 'l') { + return pad(hours12(_date), padding == null ? ' ' : padding); + } else if (_c === 'M') { + return pad(_date.getMinutes(), padding); + } else if (_c === 'm') { + return pad(_date.getMonth() + 1, padding); + } else if (_c === 'n') { + return '\n'; + } else if (_c === 'o') { + return String(_date.getDate()) + ordinal(_date.getDate()); + } else if (_c === 'P') { + return _date.getHours() < 12 ? _locale.am : _locale.pm; + } else if (_c === 'p') { + return _date.getHours() < 12 ? _locale.AM : _locale.PM; + } else if (_c === 'R') { + return _strftime(_locale.formats.R || '%H:%M', _date, _locale); + } else if (_c === 'r') { + return _strftime(_locale.formats.r || '%I:%M:%S %p', _date, _locale); + } else if (_c === 'S') { + return pad(_date.getSeconds(), padding); + } else if (_c === 's') { + return Math.floor(timestamp / 1000); + } else if (_c === 'T') { + return _strftime(_locale.formats.T || '%H:%M:%S', _date, _locale); + } else if (_c === 't') { + return '\t'; + } else if (_c === 'U') { + return pad(weekNumber(_date, 'sunday'), padding); + } else if (_c === 'u') { + day = _date.getDay(); + return day === 0 ? 7 : day; + } else if (_c === 'v') { + return _strftime(_locale.formats.v || '%e-%b-%Y', _date, _locale); + } else if (_c === 'W') { + return pad(weekNumber(_date, 'monday'), padding); + } else if (_c === 'w') { + return _date.getDay(); + } else if (_c === 'Y') { + return _date.getFullYear(); + } else if (_c === 'y') { + return String(_date.getFullYear()).slice(-2); + } else if (_c === 'Z') { + if (_options.utc) { + return 'GMT'; + } else { + var tzString = _date.toString().match(/\((\w+)\)/); + return tzString && tzString[1] || ''; + } + } else if (_c === 'z') { + if (_options.utc) { + return '+0000'; + } else { + var off = typeof _timeZone === 'number' ? _timeZone : -_date.getTimezoneOffset(); + return (off < 0 ? '-' : '+') + pad(Math.abs(off / 60)) + pad(off % 60); + } + } else { + return _c; + } } + // TODO: Did not inline quacksLikeDate called from _strftime (target requires context change). function quacksLikeDate(x) { var index = RequiredDateMethods.length; @@ -343,6 +472,10 @@ return true; } + function dateToUTC(d) { + new Date(d.getTime() + (d.getTimezoneOffset() || 0) * 60000); + } + // Default padding is '0' and default length is 2, both are optional. function pad(n, padding, length) { var _padding = padding == null ? '0' : padding;