From f36819ad992e0af4e7f2dfee9d8b0d50c47829cc Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sat, 7 Feb 2015 19:24:37 -0800 Subject: [PATCH] merge V2 code and deprecate the old API --- strftime.js | 827 +++++++++++++++++++++++++++++-------------------- strftimeV2.js | 497 ----------------------------- test/test.js | 340 ++++++++++---------- test/testV2.js | 224 -------------- 4 files changed, 663 insertions(+), 1225 deletions(-) delete mode 100644 strftimeV2.js delete mode 100644 test/testV2.js diff --git a/strftime.js b/strftime.js index cdc5d45..4efa4eb 100644 --- a/strftime.js +++ b/strftime.js @@ -11,356 +11,521 @@ ;(function() { - //// Where to export the API - var namespace; + //// Where to export the API + var namespace, + DefaultLocale = { + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + AM: 'AM', + PM: 'PM', + am: 'am', + pm: 'pm', + formats: { + D: '%m/%d/%y', + F: '%Y-%m-%d', + R: '%H:%M', + T: '%H:%M:%S', + r: '%I:%M:%S %p', + v: '%e-%b-%Y' + } + }, + + defaultStrftime = new Strftime(DefaultLocale, 0, false); - // CommonJS / Node module - if (typeof module !== 'undefined') { - namespace = module.exports = strftime; - } - - // Browsers and other environments - else { - // Get the global object. Works in ES3, ES5, and ES5 strict mode. - namespace = (function(){ return this || (1,eval)('this') }()); - } - - function words(s) { return (s || '').split(' '); } - - var DefaultLocale = - { days: words('Sunday Monday Tuesday Wednesday Thursday Friday Saturday') - , shortDays: words('Sun Mon Tue Wed Thu Fri Sat') - , months: words('January February March April May June July August September October November December') - , shortMonths: words('Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec') - , AM: 'AM' - , PM: 'PM' - , am: 'am' - , pm: 'pm' - }; - - namespace.strftime = strftime; - function strftime(fmt, d, locale) { - return _strftime(fmt, d, locale); - } - - // locale is optional - namespace.strftimeTZ = strftime.strftimeTZ = strftimeTZ; - function strftimeTZ(fmt, d, locale, timezone) { - if ((typeof locale == 'number' || typeof locale == 'string') && timezone == null) { - timezone = locale; - locale = undefined; + // CommonJS / Node module + if (typeof module !== 'undefined') { + namespace = module.exports = defaultStrftime; } - return _strftime(fmt, d, locale, { timezone: timezone }); - } - namespace.strftimeUTC = strftime.strftimeUTC = strftimeUTC; - function strftimeUTC(fmt, d, locale) { - return _strftime(fmt, d, locale, { utc: true }); - } + // Browsers and other environments + else { + // Get the global object. Works in ES3, ES5, and ES5 strict mode. + namespace = (function(){ return this || (1,eval)('this') }()); + } - namespace.localizedStrftime = strftime.localizedStrftime = localizedStrftime; - function localizedStrftime(locale) { - return function(fmt, d, options) { - return strftime(fmt, d, locale, options); + // Deprecated API, to be removed in v1.0 + var _deprecationWarnings = {}; + function deprecationWarning(name, instead) { + if (!_deprecationWarnings[name]) { + console.warn("[WARNING] `require('strftime')." + name + "` is deprecated and will be removed in version 1.0. Instead, use `" + instead + "`."); + _deprecationWarnings[name] = true; + } + } + + namespace.strftime = function(fmt, d, locale) { + deprecationWarning("strftime", "require('strftime')(format, date)` or `require('strftime').localize(locale)(format, date)"); + var strftime = locale ? defaultStrftime.localize(locale) : defaultStrftime; + return strftime(fmt, d); }; - } - - // d, locale, and options are optional, but you can't leave - // holes in the argument list. If you pass options you have to pass - // in all the preceding args as well. - // - // options: - // - locale [object] an object with the same structure as DefaultLocale - // - timezone [number] timezone offset in minutes from GMT - function _strftime(fmt, d, locale, options) { - options = options || {}; - - // d and locale are optional so check if d is really the locale - if (d && !quacksLikeDate(d)) { - locale = d; - d = undefined; - } - d = d || new Date(); - - 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; - - if (options.utc || tzType == 'number' || tzType == 'string') { - d = dateToUTC(d); - } - - if (tz) { - // 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); - tz = sign * ((60 * hours) + mins); - } - - if (tzType) { - d = new Date(d.getTime() + (tz * 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 fmt.replace(/%([-_0]?.)/g, function(_, c) { - var mod, padding; - - if (c.length == 2) { - mod = c[0]; - // omit padding - if (mod == '-') { - padding = ''; + + namespace.strftimeTZ = function(fmt, d, locale, timezone) { + deprecationWarning("strftimeTZ", "require('strftime').timezone(tz)(format, date)` or `require('strftime').timezone(tz).localize(locale)(format, date)"); + if ((typeof locale == 'number' || typeof locale == 'string') && timezone == null) { + timezone = locale; + locale = undefined; } - // pad with space - else if (mod == '_') { - padding = ' '; + var strftime = (locale ? defaultStrftime.localize(locale) : defaultStrftime).timezone(timezone); + return strftime(fmt, d); + } + + namespace.strftimeUTC = function(fmt, d, locale) { + deprecationWarning("strftimeUTC", "require('strftime').utc()(format, date)` or `require('strftime').localize(locale).utc()(format, date)"); + var strftime = (locale ? defaultStrftime.localize(locale) : defaultStrftime).utc(); + return strftime(fmt, d); + } + + namespace.localizedStrftime = function(locale) { + deprecationWarning("localizedStrftime", "require('strftime').localize(locale)"); + return defaultStrftime.localize(locale); + } + // End of deprecated API + + function Strftime(locale, customTimezoneOffset, useUtcTimezone) { + var _locale = locale || DefaultLocale, + _customTimezoneOffset = customTimezoneOffset || 0, + _useUtcBasedDate = useUtcTimezone || false, + + // we store unix timestamp value here to not create new Date() each iteration (each millisecond) + // Date.now() is 2 times faster than new Date() + // while millisecond precise is enough here + // this could be very helpful when strftime triggered a lot of times one by one + _cachedDateTimestamp = 0, + _cachedDate; + + function _strftime(format, date) { + var timestamp; + + if(!date) { + var currentTimestamp = Date.now(); + if(currentTimestamp > _cachedDateTimestamp) { + _cachedDateTimestamp = currentTimestamp; + _cachedDate = new Date(_cachedDateTimestamp); + + timestamp = _cachedDateTimestamp; + + if(_useUtcBasedDate) { + // how to avoid duplication of date instantiation for utc here? + // we tied to getTimezoneOffset of the current date + _cachedDate = new Date(_cachedDateTimestamp + getTimestampToUtcOffsetFor(_cachedDate) + _customTimezoneOffset); + } + } + date = _cachedDate; + } else { + timestamp = date.getTime(); + + if(_useUtcBasedDate) { + date = new Date(date.getTime() + getTimestampToUtcOffsetFor(date) + _customTimezoneOffset); + } + } + + return _processFormat(format, date, _locale, timestamp); } - // pad with zero - else if (mod == '0') { - padding = '0'; + + function _processFormat(format, date, locale, timestamp) { + var resultString = '', + padding = null, + isInScope = false, + length = format.length; + + for (var i = 0; i < length; i++) { + + var currentCharCode = format.charCodeAt(i); + + if(isInScope === true) { + // '-' + if (currentCharCode === 45) { + padding = ''; + continue; + } + // '_' + else if (currentCharCode === 95) { + padding = ' '; + continue; + } + // '0' + else if (currentCharCode === 48) { + padding = '0'; + continue; + } + + switch (currentCharCode) { + + // Examples for new Date(0) in GMT + + // 'Thursday' + // case 'A': + case 65: + resultString += locale.days[date.getDay()]; + break; + + // 'January' + // case 'B': + case 66: + resultString += locale.months[date.getMonth()]; + break; + + // '19' + // case 'C': + case 67: + resultString += padTill2(Math.floor(date.getFullYear() / 100), padding); + break; + + // '01/01/70' + // case 'D': + case 68: + resultString += _processFormat(locale.formats.D, date, locale, timestamp); + break; + + // '1970-01-01' + // case 'F': + case 70: + resultString += _processFormat(locale.formats.F, date, locale, timestamp); + break; + + // '00' + // case 'H': + case 72: + resultString += padTill2(date.getHours(), padding); + break; + + // '12' + // case 'I': + case 73: + resultString += padTill2(hours12(date.getHours()), padding); + break; + + // '000' + // case 'L': + case 76: + resultString += padTill3(Math.floor(timestamp % 1000)); + break; + + // '00' + // case 'M': + case 77: + resultString += padTill2(date.getMinutes(), padding); + break; + + // 'am' + // case 'P': + case 80: + resultString += date.getHours() < 12 ? locale.am : locale.pm; + break; + + // '00:00' + // case 'R': + case 82: + resultString += _processFormat(locale.formats.R, date, locale, timestamp); + break; + + // '00' + // case 'S': + case 83: + resultString += padTill2(date.getSeconds(), padding); + break; + + // '00:00:00' + // case 'T': + case 84: + resultString += _processFormat(locale.formats.T, date, locale, timestamp); + break; + + // '00' + // case 'U': + case 85: + resultString += padTill2(weekNumber(date, 'sunday'), padding); + break; + + // '00' + // case 'W': + case 87: + resultString += padTill2(weekNumber(date, 'monday'), padding); + break; + + // '1970' + // case 'Y': + case 89: + resultString += date.getFullYear(); + break; + + // 'GMT' + // case 'Z': + case 90: + if (_useUtcBasedDate && _customTimezoneOffset === 0) { + resultString += "GMT"; + } + else { + // fixme optimize + var tzString = date.toString().match(/\((\w+)\)/); + resultString += tzString && tzString[1] || ''; + } + break; + + // 'Thu' + // case 'a': + case 97: + resultString += locale.shortDays[date.getDay()]; + break; + + // 'Jan' + // case 'b': + case 98: + resultString += locale.shortMonths[date.getMonth()]; + break; + + // '01' + // case 'd': + case 100: + resultString += padTill2(date.getDate(), padding); + break; + + // '01' + // case 'e': + case 101: + resultString += date.getDate(); + break; + + // 'Jan' + // case 'h': + case 104: + resultString += locale.shortMonths[date.getMonth()]; + break; + + // '000' + // case 'j': + case 106: + var y = new Date(date.getFullYear(), 0, 1); + var day = Math.ceil((date.getTime() - y.getTime()) / (1000 * 60 * 60 * 24)); + resultString += padTill3(day); + break; + + // ' 0' + // case 'k': + case 107: + resultString += padTill2(date.getHours(), padding == null ? ' ' : padding); + break; + + // '12' + // case 'l': + case 108: + resultString += padTill2(hours12(date.getHours()), padding == null ? ' ' : padding); + break; + + // '01' + // case 'm': + case 109: + resultString += padTill2(date.getMonth() + 1, padding); + break; + + // '\n' + // case 'n': + case 110: + resultString += '\n'; + break; + + // '1st' + // case 'o': + case 111: + resultString += String(date.getDate()) + ordinal(date.getDate()); + break; + + // 'AM' + // case 'p': + case 112: + resultString += date.getHours() < 12 ? locale.AM : locale.PM; + break; + + // '12:00:00 AM' + // case 'r': + case 114: + resultString += _processFormat(locale.formats.r, date, locale, timestamp); + break; + + // '0' + // case 's': + case 115: + resultString += Math.floor(timestamp / 1000); + break; + + // '\t' + // case 't': + case 116: + resultString += '\t'; + break; + + // '4' + // case 'u': + case 117: + var day = date.getDay(); + resultString += day === 0 ? 7 : day; + break; // 1 - 7, Monday is first day of the week + + // '1-Jan-1970' + // case 'v': + case 118: + resultString += _processFormat(locale.formats.v, date, locale, timestamp); + break; + + // '4' + // case 'w': + case 119: + resultString += date.getDay(); + break; // 0 - 6, Sunday is first day of the week + + // '70' + // case 'y': + case 121: + resultString += ('' + date.getFullYear()).slice(2); + break; + + // '+0000' + // case 'z': + case 122: + if (_useUtcBasedDate && _customTimezoneOffset === 0) { + resultString += "+0000"; + } + else { + var off; + if(_customTimezoneOffset !== 0) { + off = _customTimezoneOffset / (60 * 1000); + } + else { + off = -date.getTimezoneOffset(); + } + resultString += (off < 0 ? '-' : '+') + padTill2(Math.floor(Math.abs(off / 60))) + padTill2(Math.abs(off % 60)); + } + break; + + default: + resultString += format[i]; + break; + } + + padding = null; + isInScope = false; + continue; + } + + // '%' + if (currentCharCode === 37) { + isInScope = true; + continue; + } + + resultString += format[i]; + } + + return resultString; } - else { - // unrecognized, return the format - return _; + + var strftime = _strftime; + + strftime.localize = function(locale) { + return new Strftime(locale || _locale, _customTimezoneOffset, _useUtcBasedDate); + }; + + strftime.timezone = function(timezone) { + var customTimezoneOffset = _customTimezoneOffset; + var useUtcBasedDate = _useUtcBasedDate; + + var timezoneType = typeof timezone; + if (timezoneType === 'number' || timezoneType === 'string') { + useUtcBasedDate = true; + + // ISO 8601 format timezone string, [-+]HHMM + if (timezoneType === 'string') { + var sign = timezone[0] === '-' ? -1 : 1, + hours = parseInt(timezone.slice(1, 3), 10), + minutes = parseInt(timezone.slice(3, 5), 10); + + customTimezoneOffset = sign * ((60 * hours) + minutes) * 60 * 1000; + // in minutes: 420 + } else if (timezoneType === 'number'){ + customTimezoneOffset = timezone * 60 * 1000; + } + } + + return new Strftime(_locale, customTimezoneOffset, useUtcBasedDate); + }; + + strftime.utc = function() { + return new Strftime(_locale, _customTimezoneOffset, true); + }; + + return strftime; + } + + function padTill2(numberToPad, paddingChar) { + if (paddingChar === '' || numberToPad > 9) { + return numberToPad; } - 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 pad(d.getDate(), padding == null ? ' ' : padding); - - // '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, 3); - - // ' 0' - case 'k': return pad(d.getHours(), padding == null ? ' ' : padding); - - // '000' - case 'L': return pad(Math.floor(timestamp % 1000), 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': - var y = String(d.getFullYear()); - return y.slice(y.length - 2); - - // 'GMT' - case 'Z': - if (options.utc) { - return "GMT"; - } - else { - var tzString = d.toString().match(/\(([\w\s]+)\)/); - 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.floor(Math.abs(off) / 60)) + pad(Math.abs(off) % 60); - } - - default: return c; - } - }); - } - - function dateToUTC(d) { - var msDelta = (d.getTimezoneOffset() || 0) * 60000; - return new Date(d.getTime() + msDelta); - } - - var RequiredDateMethods = ['getTime', 'getTimezoneOffset', 'getDay', 'getDate', 'getMonth', 'getFullYear', 'getYear', 'getHours', 'getMinutes', 'getSeconds']; - function quacksLikeDate(x) { - var i = 0 - , n = RequiredDateMethods.length - ; - for (i = 0; i < n; ++i) { - if (typeof x[RequiredDateMethods[i]] != 'function') { - return false; - } - } - return true; - } - - // Default padding is '0' and default length is 2, both are optional. - function pad(n, padding, length) { - // pad(n, ) - if (typeof padding === 'number') { - length = padding; - padding = '0'; + if (paddingChar == null) { + paddingChar = '0'; + } + return paddingChar + numberToPad; } - // Defaults handle pad(n) and pad(n, ) - if (padding == null) { - padding = '0'; + function padTill3(numberToPad) { + if (numberToPad > 99) { + return numberToPad; + } + if (numberToPad > 9) { + return '0' + numberToPad; + } + return '00' + numberToPad; } - length = length || 2; - var s = String(n); - // padding may be an empty string, don't loop forever if it is - if (padding) { - while (s.length < length) s = padding + s; + function hours12(hour) { + if (hour === 0) { + return 12; + } + else if (hour > 12) { + return hour - 12; + } + return hour; } - return s; - } - function hours12(d) { - var hour = d.getHours(); - if (hour == 0) hour = 12; - else if (hour > 12) hour -= 12; - return hour; - } + // firstWeekday: 'sunday' or 'monday', default is 'sunday' + // + // Pilfered & ported from Ruby's strftime implementation. + function weekNumber(date, firstWeekday) { + firstWeekday = firstWeekday || 'sunday'; - // Get the ordinal suffix for a number: st, nd, rd, or th - function ordinal(n) { - var i = n % 10 - , ii = n % 100 - ; - if ((ii >= 11 && ii <= 13) || i === 0 || i >= 4) { - return 'th'; + // This works by shifting the weekday back by one day if we + // are treating Monday as the first day of the week. + var weekday = date.getDay(); + if (firstWeekday === 'monday') { + if (weekday === 0) // Sunday + weekday = 6; + else + weekday--; + } + var firstDayOfYear = new Date(date.getFullYear(), 0, 1), + yday = (date - firstDayOfYear) / 86400000, + weekNum = (yday + 7 - weekday) / 7; + + return Math.floor(weekNum); } - switch (i) { - case 1: return 'st'; - case 2: return 'nd'; - case 3: return 'rd'; - } - } - // firstWeekday: 'sunday' or 'monday', default is 'sunday' - // - // Pilfered & ported from Ruby's strftime implementation. - function weekNumber(d, firstWeekday) { - firstWeekday = firstWeekday || 'sunday'; + // Get the ordinal suffix for a number: st, nd, rd, or th + function ordinal(number) { + var i = number % 10; + var ii = number % 100; - // This works by shifting the weekday back by one day if we - // are treating Monday as the first day of the week. - var wday = d.getDay(); - if (firstWeekday == 'monday') { - if (wday == 0) // Sunday - wday = 6; - else - wday--; + if ((ii >= 11 && ii <= 13) || i === 0 || i >= 4) { + return 'th'; + } + switch (i) { + case 1: return 'st'; + case 2: return 'nd'; + case 3: return 'rd'; + } + } + + function getTimestampToUtcOffsetFor(date) { + return (date.getTimezoneOffset() || 0) * 60000; } - var firstDayOfYear = new Date(d.getFullYear(), 0, 1) - , yday = (d - firstDayOfYear) / 86400000 - , weekNum = (yday + 7 - wday) / 7 - ; - return Math.floor(weekNum); - } }()); diff --git a/strftimeV2.js b/strftimeV2.js deleted file mode 100644 index 928a810..0000000 --- a/strftimeV2.js +++ /dev/null @@ -1,497 +0,0 @@ -// -// strftime -// github.com/samsonjs/strftime -// @_sjs -// -// Copyright 2010 - 2013 Sami Samhuri -// -// MIT License -// http://sjs.mit-license.org -// - -;(function() { - "use strict"; - - //// Where to export the API - var namespace, - - DefaultLocale = { - days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], - shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], - months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], - shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], - AM: 'AM', - PM: 'PM', - am: 'am', - pm: 'pm', - formats: { - D: '%m/%d/%y', - F: '%Y-%m-%d', - R: '%H:%M', - T: '%H:%M:%S', - r: '%I:%M:%S %p', - v: '%e-%b-%Y' - } - }, - - defaultStrftime = new Strftime(DefaultLocale, 0, false); - - // CommonJS / Node module - if (typeof module !== 'undefined') { - namespace = module.exports = defaultStrftime; - } - - // Browsers and other environments - else { - // Get the global object. Works in ES3, ES5, and ES5 strict mode. - namespace = (function(){ return this || (1,eval)('this') }()); - } - - namespace.strftime = defaultStrftime; - - function Strftime(locale, customTimezoneOffset, useUtcTimezone) { - var _locale = locale || DefaultLocale, - _customTimezoneOffset = customTimezoneOffset || 0, - _useUtcBasedDate = useUtcTimezone || false, - - // we store unix timestamp value here to not create new Date() each iteration (each millisecond) - // Date.now() is 2 times faster than new Date() - // while millisecond precise is enough here - // this could be very helpful when strftime triggered a lot of times one by one - _cachedDateTimestamp = 0, - _cachedDate; - - function _strftime(format, date) { - var timestamp; - - if(!date) { - var currentTimestamp = Date.now(); - if(currentTimestamp > _cachedDateTimestamp) { - _cachedDateTimestamp = currentTimestamp; - _cachedDate = new Date(_cachedDateTimestamp); - - timestamp = _cachedDateTimestamp; - - if(_useUtcBasedDate) { - // how to avoid duplication of date instantiation for utc here? - // we tied to getTimezoneOffset of the current date - _cachedDate = new Date(_cachedDateTimestamp + getTimestampToUtcOffsetFor(_cachedDate) + _customTimezoneOffset); - } - } - date = _cachedDate; - } else { - timestamp = date.getTime(); - - if(_useUtcBasedDate) { - date = new Date(date.getTime() + getTimestampToUtcOffsetFor(date) + _customTimezoneOffset); - } - } - - return _processFormat(format, date, _locale, timestamp); - } - - function _processFormat(format, date, locale, timestamp) { - var resultString = '', - padding = null, - isInScope = false, - length = format.length; - - for (var i = 0; i < length; i++) { - - var currentCharCode = format.charCodeAt(i); - - if(isInScope === true) { - // '-' - if (currentCharCode === 45) { - padding = ''; - continue; - } - // '_' - else if (currentCharCode === 95) { - padding = ' '; - continue; - } - // '0' - else if (currentCharCode === 48) { - padding = '0'; - continue; - } - - switch (currentCharCode) { - - // Examples for new Date(0) in GMT - - // 'Thursday' - // case 'A': - case 65: - resultString += locale.days[date.getDay()]; - break; - - // 'January' - // case 'B': - case 66: - resultString += locale.months[date.getMonth()]; - break; - - // '19' - // case 'C': - case 67: - resultString += padTill2(Math.floor(date.getFullYear() / 100), padding); - break; - - // '01/01/70' - // case 'D': - case 68: - resultString += _processFormat(locale.formats.D, date, locale, timestamp); - break; - - // '1970-01-01' - // case 'F': - case 70: - resultString += _processFormat(locale.formats.F, date, locale, timestamp); - break; - - // '00' - // case 'H': - case 72: - resultString += padTill2(date.getHours(), padding); - break; - - // '12' - // case 'I': - case 73: - resultString += padTill2(hours12(date.getHours()), padding); - break; - - // '000' - // case 'L': - case 76: - resultString += padTill3(Math.floor(timestamp % 1000)); - break; - - // '00' - // case 'M': - case 77: - resultString += padTill2(date.getMinutes(), padding); - break; - - // 'am' - // case 'P': - case 80: - resultString += date.getHours() < 12 ? locale.am : locale.pm; - break; - - // '00:00' - // case 'R': - case 82: - resultString += _processFormat(locale.formats.R, date, locale, timestamp); - break; - - // '00' - // case 'S': - case 83: - resultString += padTill2(date.getSeconds(), padding); - break; - - // '00:00:00' - // case 'T': - case 84: - resultString += _processFormat(locale.formats.T, date, locale, timestamp); - break; - - // '00' - // case 'U': - case 85: - resultString += padTill2(weekNumber(date, 'sunday'), padding); - break; - - // '00' - // case 'W': - case 87: - resultString += padTill2(weekNumber(date, 'monday'), padding); - break; - - // '1970' - // case 'Y': - case 89: - resultString += date.getFullYear(); - break; - - // 'GMT' - // case 'Z': - case 90: - if (_useUtcBasedDate && _customTimezoneOffset === 0) { - resultString += "GMT"; - } - else { - // fixme optimize - var tzString = date.toString().match(/\((\w+)\)/); - resultString += tzString && tzString[1] || ''; - } - break; - - // 'Thu' - // case 'a': - case 97: - resultString += locale.shortDays[date.getDay()]; - break; - - // 'Jan' - // case 'b': - case 98: - resultString += locale.shortMonths[date.getMonth()]; - break; - - // '01' - // case 'd': - case 100: - resultString += padTill2(date.getDate(), padding); - break; - - // '01' - // case 'e': - case 101: - resultString += date.getDate(); - break; - - // 'Jan' - // case 'h': - case 104: - resultString += locale.shortMonths[date.getMonth()]; - break; - - // '000' - // case 'j': - case 106: - var y = new Date(date.getFullYear(), 0, 1); - var day = Math.ceil((date.getTime() - y.getTime()) / (1000 * 60 * 60 * 24)); - resultString += padTill3(day); - break; - - // ' 0' - // case 'k': - case 107: - resultString += padTill2(date.getHours(), padding == null ? ' ' : padding); - break; - - // '12' - // case 'l': - case 108: - resultString += padTill2(hours12(date.getHours()), padding == null ? ' ' : padding); - break; - - // '01' - // case 'm': - case 109: - resultString += padTill2(date.getMonth() + 1, padding); - break; - - // '\n' - // case 'n': - case 110: - resultString += '\n'; - break; - - // '1st' - // case 'o': - case 111: - resultString += String(date.getDate()) + ordinal(date.getDate()); - break; - - // 'AM' - // case 'p': - case 112: - resultString += date.getHours() < 12 ? locale.AM : locale.PM; - break; - - // '12:00:00 AM' - // case 'r': - case 114: - resultString += _processFormat(locale.formats.r, date, locale, timestamp); - break; - - // '0' - // case 's': - case 115: - resultString += Math.floor(timestamp / 1000); - break; - - // '\t' - // case 't': - case 116: - resultString += '\t'; - break; - - // '4' - // case 'u': - case 117: - var day = date.getDay(); - resultString += day === 0 ? 7 : day; - break; // 1 - 7, Monday is first day of the week - - // '1-Jan-1970' - // case 'v': - case 118: - resultString += _processFormat(locale.formats.v, date, locale, timestamp); - break; - - // '4' - // case 'w': - case 119: - resultString += date.getDay(); - break; // 0 - 6, Sunday is first day of the week - - // '70' - // case 'y': - case 121: - resultString += ('' + date.getFullYear()).slice(2); - break; - - // '+0000' - // case 'z': - case 122: - if (_useUtcBasedDate && _customTimezoneOffset === 0) { - resultString += "+0000"; - } - else { - var off; - if(_customTimezoneOffset !== 0) { - off = _customTimezoneOffset / (60 * 1000); - } - else { - off = -date.getTimezoneOffset(); - } - resultString += (off < 0 ? '-' : '+') + padTill2(Math.floor(Math.abs(off / 60))) + padTill2(Math.abs(off % 60)); - } - break; - - default: - resultString += format[i]; - break; - } - - padding = null; - isInScope = false; - continue; - } - - // '%' - if (currentCharCode === 37) { - isInScope = true; - continue; - } - - resultString += format[i]; - } - - return resultString; - } - - var strftime = _strftime; - - strftime.setLocaleTo = function(locale) { - return new Strftime(locale || _locale, _customTimezoneOffset, _useUtcBasedDate); - }; - - strftime.setTimezoneTo = function(timezone) { - var customTimezoneOffset = _customTimezoneOffset; - var useUtcBasedDate = _useUtcBasedDate; - - var timezoneType = typeof timezone; - if (timezoneType === 'number' || timezoneType === 'string') { - useUtcBasedDate = true; - - // ISO 8601 format timezone string, [-+]HHMM - if (timezoneType === 'string') { - var sign = timezone[0] === '-' ? -1 : 1, - hours = parseInt(timezone.slice(1, 3), 10), - minutes = parseInt(timezone.slice(3, 5), 10); - - customTimezoneOffset = sign * ((60 * hours) + minutes) * 60 * 1000; - // in minutes: 420 - } else if (timezoneType === 'number'){ - customTimezoneOffset = timezone * 60 * 1000; - } - } - - return new Strftime(_locale, customTimezoneOffset, useUtcBasedDate); - }; - - strftime.useUTC = function() { - return new Strftime(_locale, _customTimezoneOffset, true); - }; - - return strftime; - } - - function padTill2(numberToPad, paddingChar) { - if (paddingChar === '' || numberToPad > 9) { - return numberToPad; - } - if (paddingChar == null) { - paddingChar = '0'; - } - return paddingChar + numberToPad; - } - - function padTill3(numberToPad) { - if (numberToPad > 99) { - return numberToPad; - } - if (numberToPad > 9) { - return '0' + numberToPad; - } - return '00' + numberToPad; - } - - function hours12(hour) { - if (hour === 0) { - return 12; - } - else if (hour > 12) { - return hour - 12; - } - return hour; - } - - // firstWeekday: 'sunday' or 'monday', default is 'sunday' - // - // Pilfered & ported from Ruby's strftime implementation. - function weekNumber(date, firstWeekday) { - firstWeekday = firstWeekday || 'sunday'; - - // This works by shifting the weekday back by one day if we - // are treating Monday as the first day of the week. - var weekday = date.getDay(); - if (firstWeekday === 'monday') { - if (weekday === 0) // Sunday - weekday = 6; - else - weekday--; - } - var firstDayOfYear = new Date(date.getFullYear(), 0, 1), - yday = (date - firstDayOfYear) / 86400000, - weekNum = (yday + 7 - weekday) / 7; - - return Math.floor(weekNum); - } - - // Get the ordinal suffix for a number: st, nd, rd, or th - function ordinal(number) { - var i = number % 10; - var ii = number % 100; - - if ((ii >= 11 && ii <= 13) || i === 0 || i >= 4) { - return 'th'; - } - switch (i) { - case 1: return 'st'; - case 2: return 'nd'; - case 3: return 'rd'; - } - } - - function getTimestampToUtcOffsetFor(date) { - return (date.getTimezoneOffset() || 0) * 60000; - } -}()); diff --git a/test/test.js b/test/test.js index 48572cd..9ff0153 100755 --- a/test/test.js +++ b/test/test.js @@ -6,219 +6,213 @@ // - past and future dates, especially < 1900 and > 2100 // - look for edge cases -var assert = require('assert') - , libFilename = process.argv[2] || '../strftime.js' - , lib = require(libFilename) - - // Tue, 07 Jun 2011 18:51:45 GMT - , TestTime = new Date(1307472705067) +var assert = require('assert'), + libFilename = process.argv[2] || '../strftime.js', + strftime = require(libFilename), + strftimeUTC = strftime.utc(), + Time = new Date(1307472705067); // Tue, 07 Jun 2011 18:51:45 GMT assert.fn = function(value, msg) { - assert.equal('function', typeof value, msg) + assert.equal('function', typeof value, msg); +}; + +function assertFormat(time, format, expected, name, strftime) { + var actual = strftime(format, time); + assert.equal(expected, actual, + name + '("' + format + '", ' + time + ') is ' + JSON.stringify(actual) + + ', expected ' + JSON.stringify(expected)); } assert.format = function(format, expected, expectedUTC, time) { - time = time || TestTime - function _assertFmt(expected, name) { - name = name || 'strftime' - var actual = lib[name](format, time) - assert.equal(expected, actual, - name + '("' + format + '", ' + time + ') is ' + JSON.stringify(actual) - + ', expected ' + JSON.stringify(expected)) - } + time = time || Time; + if (expected) { assertFormat(time, format, expected, 'strftime', strftime); } + assertFormat(time, format, expectedUTC || expected, 'strftime.utc()', strftimeUTC); +}; - if (expected) _assertFmt(expected, 'strftime') - _assertFmt(expectedUTC || expected, 'strftimeUTC') -} +/// check deprecated exports +assert.fn(strftime.strftime); +assert.fn(strftime.strftimeTZ); +assert.fn(strftime.strftimeUTC); +assert.fn(strftime.localizedStrftime); +ok('Deprecated exports'); /// check exports -assert.fn(lib.strftime) -assert.fn(lib.strftimeUTC) -assert.fn(lib.localizedStrftime) -ok('Exports') +assert.fn(strftime.localize); +assert.fn(strftime.timezone); +assert.fn(strftime.utc); +ok('Exports'); /// time zones if (process.env.TZ == 'America/Vancouver') { - testTimezone('P[DS]T') - assert.format('%C', '01', '01', new Date(100, 0, 1)) - assert.format('%j', '097', '098', new Date(1365390736236)) - ok('Time zones (' + process.env.TZ + ')') + testTimezone('P[DS]T'); + assert.format('%C', '01', '01', new Date(100, 0, 1)); + assert.format('%j', '097', '098', new Date(1365390736236)); + ok('Time zones (' + process.env.TZ + ')'); } else if (process.env.TZ == 'CET') { - testTimezone('CES?T') - assert.format('%C', '01', '00', new Date(100, 0, 1)) - assert.format('%j', '098', '098', new Date(1365390736236)) - ok('Time zones (' + process.env.TZ + ')') + testTimezone('CES?T'); + assert.format('%C', '01', '00', new Date(100, 0, 1)); + assert.format('%j', '098', '098', new Date(1365390736236)); + ok('Time zones (' + process.env.TZ + ')'); } else { - console.log('(Current timezone has no tests: ' + (process.env.TZ || 'none') + ')') + console.log('(Current timezone has no tests: ' + (process.env.TZ || 'none') + ')'); } /// check all formats in GMT, most coverage -assert.format('%A', 'Tuesday') -assert.format('%a', 'Tue') -assert.format('%B', 'June') -assert.format('%b', 'Jun') -assert.format('%C', '20') -assert.format('%D', '06/07/11') -assert.format('%d', '07') -assert.format('%-d', '7') -assert.format('%_d', ' 7') -assert.format('%0d', '07') -assert.format('%e', ' 7') -assert.format('%F', '2011-06-07') -assert.format('%H', null, '18') -assert.format('%h', 'Jun') -assert.format('%I', null, '06') -assert.format('%-I', null, '6') -assert.format('%_I', null, ' 6') -assert.format('%0I', null, '06') -assert.format('%j', null, '158') -assert.format('%k', null, '18') -assert.format('%L', '067') -assert.format('%l', null, ' 6') -assert.format('%-l', null, '6') -assert.format('%_l', null, ' 6') -assert.format('%0l', null, '06') -assert.format('%M', null, '51') -assert.format('%m', '06') -assert.format('%n', '\n') -assert.format('%o', '7th') -assert.format('%P', null, 'pm') -assert.format('%p', null, 'PM') -assert.format('%R', null, '18:51') -assert.format('%r', null, '06:51:45 PM') -assert.format('%S', '45') -assert.format('%s', '1307472705') -assert.format('%T', null, '18:51:45') -assert.format('%t', '\t') -assert.format('%U', '23') -assert.format('%U', '24', null, new Date(+TestTime + 5 * 86400000)) -assert.format('%u', '2') -assert.format('%v', ' 7-Jun-2011') -assert.format('%W', '23') -assert.format('%W', '23', null, new Date(+TestTime + 5 * 86400000)) -assert.format('%w', '2') -assert.format('%Y', '2011') -assert.format('%y', '11') -assert.format('%Z', null, 'GMT') -assert.format('%z', null, '+0000') -assert.format('%%', '%') // any other char -ok('GMT') +assert.format('%A', 'Tuesday'); +assert.format('%a', 'Tue'); +assert.format('%B', 'June'); +assert.format('%b', 'Jun'); +assert.format('%C', '20'); +assert.format('%D', '06/07/11'); +assert.format('%d', '07'); +assert.format('%-d', '7'); +assert.format('%_d', ' 7'); +assert.format('%0d', '07'); +assert.format('%e', '7'); +assert.format('%F', '2011-06-07'); +assert.format('%H', null, '18'); +assert.format('%h', 'Jun'); +assert.format('%I', null, '06'); +assert.format('%-I', null, '6'); +assert.format('%_I', null, ' 6'); +assert.format('%0I', null, '06'); +assert.format('%j', null, '158'); +assert.format('%k', null, '18'); +assert.format('%L', '067'); +assert.format('%l', null, ' 6'); +assert.format('%-l', null, '6'); +assert.format('%_l', null, ' 6'); +assert.format('%0l', null, '06'); +assert.format('%M', null, '51'); +assert.format('%m', '06'); +assert.format('%n', '\n'); +assert.format('%o', '7th'); +assert.format('%P', null, 'pm'); +assert.format('%p', null, 'PM'); +assert.format('%R', null, '18:51'); +assert.format('%r', null, '06:51:45 PM'); +assert.format('%S', '45'); +assert.format('%s', '1307472705'); +assert.format('%T', null, '18:51:45'); +assert.format('%t', '\t'); +assert.format('%U', '23'); +assert.format('%U', '24', null, new Date(+Time + 5 * 86400000)); +assert.format('%u', '2'); +assert.format('%v', '7-Jun-2011'); +assert.format('%W', '23'); +assert.format('%W', '23', null, new Date(+Time + 5 * 86400000)); +assert.format('%w', '2'); +assert.format('%Y', '2011'); +assert.format('%y', '11'); +assert.format('%Z', null, 'GMT'); +assert.format('%z', null, '+0000'); +assert.format('%%', '%') // any other char; +ok('GMT'); /// locales -var it_IT = -{ days: words('domenica lunedi martedi mercoledi giovedi venerdi sabato') -, shortDays: words('dom lun mar mer gio ven sab') -, months: words('gennaio febbraio marzo aprile maggio giugno luglio agosto settembre ottobre novembre dicembre') -, shortMonths: words('gen feb mar apr mag giu lug ago set ott nov dic') -, AM: 'it$AM' -, PM: 'it$PM' -, am: 'it$am' -, pm: 'it$pm' -, formats: { - D: 'it$%m/%d/%y' - , F: 'it$%Y-%m-%d' - , R: 'it$%H:%M' - , r: 'it$%I:%M:%S %p' - , T: 'it$%H:%M:%S' - , v: 'it$%e-%b-%Y' - } -} +var it_IT = { + days: words('domenica lunedi martedi mercoledi giovedi venerdi sabato'), + shortDays: words('dom lun mar mer gio ven sab'), + months: words('gennaio febbraio marzo aprile maggio giugno luglio agosto settembre ottobre novembre dicembre'), + shortMonths: words('gen feb mar apr mag giu lug ago set ott nov dic'), + AM: 'it$AM', + PM: 'it$PM', + am: 'it$am', + pm: 'it$pm', + formats: { + D: 'it$%m/%d/%y', + F: 'it$%Y-%m-%d', + R: 'it$%H:%M', + r: 'it$%I:%M:%S %p', + T: 'it$%H:%M:%S', + v: 'it$%e-%b-%Y' + } + }; +var strftimeIT = strftime.localize(it_IT), + strftimeITUTC = strftimeIT.utc(); assert.format_it = function(format, expected, expectedUTC) { - function _assertFmt(expected, name) { - name = name || 'strftime' - var actual = lib[name](format, TestTime, it_IT) - assert.equal(expected, actual, - name + '("' + format + '", Time) is ' + JSON.stringify(actual) - + ', expected ' + JSON.stringify(expected)) - } + if (expected) { assertFormat(Time, format, expected, 'strftime.localize(it_IT)', strftimeIT) } + assertFormat(Time, format, expectedUTC || expected, 'strftime.localize(it_IT).utc()', strftimeITUTC) +}; - if (expected) _assertFmt(expected, 'strftime') - _assertFmt(expectedUTC || expected, 'strftimeUTC') -} - -assert.format_it('%A', 'martedi') -assert.format_it('%a', 'mar') -assert.format_it('%B', 'giugno') -assert.format_it('%b', 'giu') -assert.format_it('%D', 'it$06/07/11') -assert.format_it('%F', 'it$2011-06-07') -assert.format_it('%p', null, 'it$PM') -assert.format_it('%P', null, 'it$pm') -assert.format_it('%R', null, 'it$18:51') -assert.format_it('%r', null, 'it$06:51:45 it$PM') -assert.format_it('%T', null, 'it$18:51:45') -assert.format_it('%v', 'it$ 7-giu-2011') -ok('Localization') +assert.format_it('%A', 'martedi'); +assert.format_it('%a', 'mar'); +assert.format_it('%B', 'giugno'); +assert.format_it('%b', 'giu'); +assert.format_it('%D', 'it$06/07/11'); +assert.format_it('%F', 'it$2011-06-07'); +assert.format_it('%p', null, 'it$PM'); +assert.format_it('%P', null, 'it$pm'); +assert.format_it('%R', null, 'it$18:51'); +assert.format_it('%r', null, 'it$06:51:45 it$PM'); +assert.format_it('%T', null, 'it$18:51:45'); +assert.format_it('%v', 'it$7-giu-2011'); +ok('Localization'); /// timezones assert.formatTZ = function(format, expected, tz, time) { - time = time || TestTime; - var actual = lib.strftimeTZ(format, time, tz) - assert.equal( - expected, actual, - ('strftime("' + format + '", ' + time + ') is ' + JSON.stringify(actual) + ', expected ' + JSON.stringify(expected)) - ) -} + assertFormat(time || Time, format, expected, 'strftime.timezone(' + tz + ')', strftime.timezone(tz)); +}; -assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', 0) -assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', '+0000') -assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', 120) -assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', '+0200') -assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', -420) -assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', '-0700') -assert.formatTZ('%F %r %z', '2011-06-07 11:21:45 AM -0730', '-0730') -ok('Time zone offset') +assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', 0); +assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', '+0000'); +assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', 120); +assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', '+0200'); +assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', -420); +assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', '-0700'); +assert.formatTZ('%F %r %z', '2011-06-07 11:21:45 AM -0730', '-0730'); +ok('Time zone offset'); /// helpers function words(s) { return (s || '').split(' '); } -function ok(s) { console.log('[ \033[32mOK\033[0m ] ' + s) } +function ok(s) { console.log('[ \033[32mOK\033[0m ] ' + s); } // Pass a regex or string that matches the timezone abbrev, e.g. %Z above. // Don't pass GMT! Every date includes it and it will fail. // Be careful if you pass a regex, it has to quack like the default one. function testTimezone(regex) { - regex = typeof regex === 'string' ? RegExp('\\((' + regex + ')\\)$') : regex - var match = TestTime.toString().match(regex) - if (match) { - var off = TestTime.getTimezoneOffset() - , hourOff = off / 60 - , hourDiff = Math.floor(hourOff) - , hours = 18 - hourDiff - , padSpace24 = hours < 10 ? ' ' : '' - , padZero24 = hours < 10 ? '0' : '' - , hour24 = String(hours) - , padSpace12 = (hours % 12) < 10 ? ' ' : '' - , padZero12 = (hours % 12) < 10 ? '0' : '' - , hour12 = String(hours % 12) - , sign = hourDiff < 0 ? '+' : '-' - , minDiff = TestTime.getTimezoneOffset() - (hourDiff * 60) - , mins = String(51 - minDiff) - , tz = match[1] - , ampm = hour12 == hour24 ? 'AM' : 'PM' - , R = hour24 + ':' + mins - , r = padZero12 + hour12 + ':' + mins + ':45 ' + ampm - , T = R + ':45' - assert.format('%H', padZero24 + hour24, '18') - assert.format('%I', padZero12 + hour12, '06') - assert.format('%k', padSpace24 + hour24, '18') - assert.format('%l', padSpace12 + hour12, ' 6') - assert.format('%M', mins) - assert.format('%P', ampm.toLowerCase(), 'pm') - assert.format('%p', ampm, 'PM') - assert.format('%R', R, '18:51') - assert.format('%r', r, '06:51:45 PM') - assert.format('%T', T, '18:51:45') - assert.format('%Z', tz, 'GMT') - assert.format('%z', sign + '0' + Math.abs(hourDiff) + '00', '+0000') - } + regex = typeof regex === 'string' ? RegExp('\\((' + regex + ')\\)$') : regex; + var match = Time.toString().match(regex); + if (match) { + var off = Time.getTimezoneOffset(), + hourOff = off / 60, + hourDiff = Math.floor(hourOff), + hours = 18 - hourDiff, + padSpace24 = hours < 10 ? ' ' : '', + padZero24 = hours < 10 ? '0' : '', + hour24 = String(hours), + padSpace12 = (hours % 12) < 10 ? ' ' : '', + padZero12 = (hours % 12) < 10 ? '0' : '', + hour12 = String(hours % 12), + sign = hourDiff < 0 ? '+' : '-', + minDiff = Time.getTimezoneOffset() - (hourDiff * 60), + mins = String(51 - minDiff), + tz = match[1], + ampm = hour12 == hour24 ? 'AM' : 'PM', + R = hour24 + ':' + mins, + r = padZero12 + hour12 + ':' + mins + ':45 ' + ampm, + T = R + ':45'; + assert.format('%H', padZero24 + hour24, '18'); + assert.format('%I', padZero12 + hour12, '06'); + assert.format('%k', padSpace24 + hour24, '18'); + assert.format('%l', padSpace12 + hour12, ' 6'); + assert.format('%M', mins); + assert.format('%P', ampm.toLowerCase(), 'pm'); + assert.format('%p', ampm, 'PM'); + assert.format('%R', R, '18:51'); + assert.format('%r', r, '06:51:45 PM'); + assert.format('%T', T, '18:51:45'); + assert.format('%Z', tz, 'GMT'); + assert.format('%z', sign + '0' + Math.abs(hourDiff) + '00', '+0000'); + } } diff --git a/test/testV2.js b/test/testV2.js deleted file mode 100644 index 61d0f6c..0000000 --- a/test/testV2.js +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env node - -// Based on CoffeeScript by andrewschaaf on github -// -// TODO: -// - past and future dates, especially < 1900 and > 2100 -// - locales -// - look for edge cases - -var assert = require('assert') - , libFilename = process.argv[2] || '../strftimeV2.js' - , lib = require(libFilename) - -// Tue, 07 Jun 2011 18:51:45 GMT - , Time = new Date(1307472705067) - -assert.fn = function(value, msg) { - assert.equal('function', typeof value, msg) -} - -assert.format = function(format, expected, expectedUTC, time) { - time = time || Time - function _assertFmt(expected, name, strftime) { - name = name || 'strftime' - var actual = strftime(format, time) - assert.equal(expected, actual, - name + '("' + format + '", ' + time + ') is ' + JSON.stringify(actual) - + ', expected ' + JSON.stringify(expected)) - } - - if (expected) _assertFmt(expected, 'strftime', lib) - _assertFmt(expectedUTC || expected, 'strftime.UseUTC()', lib.useUTC()) -} - -/// check exports -assert.fn(lib.strftime) -ok('Exports') - -/// time zones -if (process.env.TZ == 'America/Vancouver') { - testTimezone('P[DS]T') - assert.format('%C', '01', '01', new Date(100, 0, 1)) - assert.format('%j', '097', '098', new Date(1365390736236)) - ok('Time zones (' + process.env.TZ + ')') -} -else if (process.env.TZ == 'CET') { - testTimezone('CES?T') - assert.format('%C', '01', '00', new Date(100, 0, 1)) - assert.format('%j', '098', '098', new Date(1365390736236)) - ok('Time zones (' + process.env.TZ + ')') -} -else { - console.log('(Current timezone has no tests: ' + (process.env.TZ || 'none') + ')') -} - -/// check all formats in GMT, most coverage -assert.format('%A', 'Tuesday') -assert.format('%a', 'Tue') -assert.format('%B', 'June') -assert.format('%b', 'Jun') -assert.format('%C', '20') -assert.format('%D', '06/07/11') -assert.format('%d', '07') -assert.format('%-d', '7') -assert.format('%_d', ' 7') -assert.format('%0d', '07') -assert.format('%e', '7') -assert.format('%F', '2011-06-07') -assert.format('%H', null, '18') -assert.format('%h', 'Jun') -assert.format('%I', null, '06') -assert.format('%-I', null, '6') -assert.format('%_I', null, ' 6') -assert.format('%0I', null, '06') -assert.format('%j', null, '158') -assert.format('%k', null, '18') -assert.format('%L', '067') -assert.format('%l', null, ' 6') -assert.format('%-l', null, '6') -assert.format('%_l', null, ' 6') -assert.format('%0l', null, '06') -assert.format('%M', null, '51') -assert.format('%m', '06') -assert.format('%n', '\n') -assert.format('%o', '7th') -assert.format('%P', null, 'pm') -assert.format('%p', null, 'PM') -assert.format('%R', null, '18:51') -assert.format('%r', null, '06:51:45 PM') -assert.format('%S', '45') -assert.format('%s', '1307472705') -assert.format('%T', null, '18:51:45') -assert.format('%t', '\t') -assert.format('%U', '23') -assert.format('%U', '24', null, new Date(+Time + 5 * 86400000)) -assert.format('%u', '2') -assert.format('%v', '7-Jun-2011') -assert.format('%W', '23') -assert.format('%W', '23', null, new Date(+Time + 5 * 86400000)) -assert.format('%w', '2') -assert.format('%Y', '2011') -assert.format('%y', '11') -assert.format('%Z', null, 'GMT') -assert.format('%z', null, '+0000') -assert.format('%%', '%') // any other char -//assert.format('%--', '-') -ok('GMT') - - -/// locales - -var it_IT = -{ days: words('domenica lunedi martedi mercoledi giovedi venerdi sabato') - , shortDays: words('dom lun mar mer gio ven sab') - , months: words('gennaio febbraio marzo aprile maggio giugno luglio agosto settembre ottobre novembre dicembre') - , shortMonths: words('gen feb mar apr mag giu lug ago set ott nov dic') - , AM: 'it$AM' - , PM: 'it$PM' - , am: 'it$am' - , pm: 'it$pm' - , formats: { - D: 'it$%m/%d/%y' - , F: 'it$%Y-%m-%d' - , R: 'it$%H:%M' - , r: 'it$%I:%M:%S %p' - , T: 'it$%H:%M:%S' - , v: 'it$%e-%b-%Y' -} -} - -assert.format_it = function(format, expected, expectedUTC) { - function _assertFmt(expected, name, strftime) { - name = name || 'strftime' - var actual = strftime(format, Time) - assert.equal(expected, actual, - name + '("' + format + '", Time) is ' + JSON.stringify(actual) - + ', expected ' + JSON.stringify(expected)) - } - - if (expected) _assertFmt(expected, 'strftime', lib.setLocaleTo(it_IT)) - _assertFmt(expectedUTC || expected, 'strftimeUTC', lib.useUTC().setLocaleTo(it_IT)) -} - -assert.format_it('%A', 'martedi') -assert.format_it('%a', 'mar') -assert.format_it('%B', 'giugno') -assert.format_it('%b', 'giu') -assert.format_it('%D', 'it$06/07/11') -assert.format_it('%F', 'it$2011-06-07') -assert.format_it('%p', null, 'it$PM') -assert.format_it('%P', null, 'it$pm') -assert.format_it('%R', null, 'it$18:51') -assert.format_it('%r', null, 'it$06:51:45 it$PM') -assert.format_it('%T', null, 'it$18:51:45') -assert.format_it('%v', 'it$7-giu-2011') -ok('Localization') - - -/// timezones - -assert.formatTZ = function(format, expected, tz, time) { - time = time || Time; - var actual = lib.setTimezoneTo(tz)(format, time) - assert.equal( - expected, actual, - ('strftime.setTimezoneTo()("' + format + '", ' + time + ') is ' + JSON.stringify(actual) + ', expected ' + JSON.stringify(expected)) - ) -} - -assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', 0) -assert.formatTZ('%F %r %z', '2011-06-07 06:51:45 PM +0000', '+0000') -assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', 120) -assert.formatTZ('%F %r %z', '2011-06-07 08:51:45 PM +0200', '+0200') -assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', -420) -assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', '-0700') -assert.formatTZ('%F %r %z', '2011-06-07 11:21:45 AM -0730', '-0730') -ok('Time zone offset') - - -/// helpers - -function words(s) { return (s || '').split(' '); } - -function ok(s) { console.log('[ \033[32mOK\033[0m ] ' + s) } - -// Pass a regex or string that matches the timezone abbrev, e.g. %Z above. -// Don't pass GMT! Every date includes it and it will fail. -// Be careful if you pass a regex, it has to quack like the default one. -function testTimezone(regex) { - regex = typeof regex === 'string' ? RegExp('\\((' + regex + ')\\)$') : regex - var match = Time.toString().match(regex) - if (match) { - var off = Time.getTimezoneOffset() - , hourOff = off / 60 - , hourDiff = Math.floor(hourOff) - , hours = 18 - hourDiff - , padSpace24 = hours < 10 ? ' ' : '' - , padZero24 = hours < 10 ? '0' : '' - , hour24 = String(hours) - , padSpace12 = (hours % 12) < 10 ? ' ' : '' - , padZero12 = (hours % 12) < 10 ? '0' : '' - , hour12 = String(hours % 12) - , sign = hourDiff < 0 ? '+' : '-' - , minDiff = Time.getTimezoneOffset() - (hourDiff * 60) - , mins = String(51 - minDiff) - , tz = match[1] - , ampm = hour12 == hour24 ? 'AM' : 'PM' - , R = hour24 + ':' + mins - , r = padZero12 + hour12 + ':' + mins + ':45 ' + ampm - , T = R + ':45' - assert.format('%H', padZero24 + hour24, '18') - assert.format('%I', padZero12 + hour12, '06') - assert.format('%k', padSpace24 + hour24, '18') - assert.format('%l', padSpace12 + hour12, ' 6') - assert.format('%M', mins) - assert.format('%P', ampm.toLowerCase(), 'pm') - assert.format('%p', ampm, 'PM') - assert.format('%R', R, '18:51') - assert.format('%r', r, '06:51:45 PM') - assert.format('%T', T, '18:51:45') - assert.format('%Z', tz, 'GMT') - assert.format('%z', sign + '0' + Math.abs(hourDiff) + '00', '+0000') - } -}