From 1633e4007c566b92406056b911e4dc47ef1141f3 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Mon, 14 Oct 2013 18:43:00 -0700 Subject: [PATCH 1/2] add support for passing in timezones --- Readme.md | 7 +++++ strftime.js | 77 +++++++++++++++++++++++++++++++++++----------------- test/test.js | 21 ++++++++++++-- 3 files changed, 78 insertions(+), 27 deletions(-) diff --git a/Readme.md b/Readme.md index 8f8bf1d..6cecbab 100644 --- a/Readme.md +++ b/Readme.md @@ -46,6 +46,13 @@ And if you don't want to pass a localization object every time you can get a loc console.log(strftime_IT('%B %d, %y %H:%M:%S')) // aprile 28, 2011 18:21:08 +Time zones can be passed in as an offset from GMT in minutes. + + var strftimeTZ = require('strftime').strftimeTZ + console.log(strftimeTZ('%B %d, %y %H:%M:%S', new Date(1307472705067), -420)) // => June 07, 11 11:51:45 + console.log(strftimeTZ('%F %T', new Date(1307472705067), 120)) // => 2011-06-07 11:51:45 + + Supported Specifiers ==================== diff --git a/strftime.js b/strftime.js index f817ae4..fa8e0df 100644 --- a/strftime.js +++ b/strftime.js @@ -11,7 +11,7 @@ ;(function() { - //// Export the API + //// Where to export the API var namespace; // CommonJS / Node module @@ -25,12 +25,6 @@ namespace = (function(){ return this || (1,eval)('this') }()); } - namespace.strftime = strftime; - namespace.strftimeUTC = strftime.strftimeUTC = strftimeUTC; - namespace.localizedStrftime = strftime.localizedStrftime = localizedStrftime; - - //// - function words(s) { return (s || '').split(' '); } var DefaultLocale = @@ -44,34 +38,62 @@ , pm: 'pm' }; + namespace.strftime = strftime; function strftime(fmt, d, locale) { - return _strftime(fmt, d, locale, false); + return _strftime(fmt, d, locale); } + // locale is optional + namespace.strftimeTZ = strftime.strftimeTZ = strftimeTZ; + function strftimeTZ(fmt, d, locale, timezone) { + if (typeof locale == 'number' && timezone == null) { + timezone = locale; + locale = undefined; + } + return _strftime(fmt, d, locale, { timezone: timezone }); + } + + namespace.strftimeUTC = strftime.strftimeUTC = strftimeUTC; function strftimeUTC(fmt, d, locale) { - return _strftime(fmt, d, locale, true); + return _strftime(fmt, d, locale, { utc: true }); } + namespace.localizedStrftime = strftime.localizedStrftime = localizedStrftime; function localizedStrftime(locale) { - return function(fmt, d) { - return strftime(fmt, d, locale); + return function(fmt, d, options) { + return strftime(fmt, d, locale, options); }; } - // locale is an object with the same structure as DefaultLocale - function _strftime(fmt, d, locale, _useUTC) { + // 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 || {}; - var msDelta = 0; - if (_useUTC) { - msDelta = (d.getTimezoneOffset() || 0) * 60000; - d = new Date(d.getTime() + msDelta); + + // Hang on to this Unix timestamp because we might mess with it directly below. + var timestamp = d.getTime(); + + if (options.utc || typeof options.timezone == 'number') { + d = dateToUTC(d); + } + + if (typeof options.timezone == 'number') { + d = new Date(d.getTime() + (options.timezone * 60000)); } // Most of the specifiers supported by C's strftime, and some from Ruby. @@ -113,11 +135,11 @@ case 'h': return locale.shortMonths[d.getMonth()]; case 'I': return pad(hours12(d), padding); case 'j': - var y=new Date(d.getFullYear(), 0, 1); - var day = Math.ceil((d.getTime() - y.getTime()) / (1000*60*60*24)); + var y = new Date(d.getFullYear(), 0, 1); + var day = Math.ceil((d.getTime() - y.getTime()) / (1000 * 60 * 60 * 24)); return pad(day, 3); case 'k': return pad(d.getHours(), padding == null ? ' ' : padding); - case 'L': return pad(Math.floor(d.getTime() % 1000), 3); + case 'L': return pad(Math.floor(timestamp % 1000), 3); case 'l': return pad(hours12(d), padding == null ? ' ' : padding); case 'M': return pad(d.getMinutes(), padding); case 'm': return pad(d.getMonth() + 1, padding); @@ -128,7 +150,7 @@ case 'R': return _strftime(locale.formats.R || '%H:%M', d, locale); case 'r': return _strftime(locale.formats.r || '%I:%M:%S %p', d, locale); case 'S': return pad(d.getSeconds(), padding); - case 's': return Math.floor((d.getTime() - msDelta) / 1000); + case 's': return Math.floor(timestamp / 1000); case 'T': return _strftime(locale.formats.T || '%H:%M:%S', d, locale); case 't': return '\t'; case 'U': return pad(weekNumber(d, 'sunday'), padding); @@ -143,7 +165,7 @@ var y = String(d.getFullYear()); return y.slice(y.length - 2); case 'Z': - if (_useUTC) { + if (options.utc) { return "GMT"; } else { @@ -151,18 +173,23 @@ return tz && tz[1] || ''; } case 'z': - if (_useUTC) { + if (options.utc) { return "+0000"; } else { - var off = d.getTimezoneOffset(); - return (off < 0 ? '+' : '-') + pad(Math.abs(off / 60)) + pad(off % 60); + var off = typeof options.timezone == 'number' ? options.timezone : -d.getTimezoneOffset(); + return (off < 0 ? '-' : '+') + pad(Math.abs(off / 60)) + pad(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 diff --git a/test/test.js b/test/test.js index 3df8fdf..c76acef 100755 --- a/test/test.js +++ b/test/test.js @@ -28,7 +28,7 @@ assert.format = function(format, expected, expectedUTC, time) { + ', expected ' + JSON.stringify(expected)) } - if (expected) _assertFmt(expected) + if (expected) _assertFmt(expected, 'strftime') _assertFmt(expectedUTC || expected, 'strftimeUTC') } @@ -138,7 +138,7 @@ assert.format_it = function(format, expected, expectedUTC) { + ', expected ' + JSON.stringify(expected)) } - if (expected) _assertFmt(expected) + if (expected) _assertFmt(expected, 'strftime') _assertFmt(expectedUTC || expected, 'strftimeUTC') } @@ -157,6 +157,23 @@ assert.format_it('%v', 'it$7-giu-2011') ok('Localization') +/// timezones + +assert.formatTZ = function(format, expected, tz, time) { + time = time || Time; + var actual = lib.strftimeTZ(format, time, tz) + assert.equal( + expected, actual, + ('strftime("' + 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 08:51:45 PM +0200', 120) +assert.formatTZ('%F %r %z', '2011-06-07 11:51:45 AM -0700', -420) +ok('Time zone offset') + + /// helpers function words(s) { return (s || '').split(' '); } From 303667d059974b4e3c0c429024c85fb2d884de74 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Mon, 14 Oct 2013 18:50:09 -0700 Subject: [PATCH 2/2] fix Readme error --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 6cecbab..e8a8083 100644 --- a/Readme.md +++ b/Readme.md @@ -50,7 +50,7 @@ Time zones can be passed in as an offset from GMT in minutes. var strftimeTZ = require('strftime').strftimeTZ console.log(strftimeTZ('%B %d, %y %H:%M:%S', new Date(1307472705067), -420)) // => June 07, 11 11:51:45 - console.log(strftimeTZ('%F %T', new Date(1307472705067), 120)) // => 2011-06-07 11:51:45 + console.log(strftimeTZ('%F %T', new Date(1307472705067), 120)) // => 2011-06-07 20:51:45 Supported Specifiers