This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Merge branch 'feature/timezone' into dev

  • Loading branch information...
paularmstrong committed Dec 30, 2011
2 parents 77ee516 + df4097e commit 4ff52231dace22dd225a100df0e39c21ac989aae
Showing with 178 additions and 26 deletions.
  1. +7 −3 docs/filters.md
  2. +6 −1 docs/getting-started.md
  3. +5 −1 index.js
  4. +99 −4 lib/dateformat.js
  5. +7 −3 lib/filters.js
  6. +54 −14 tests/filters.test.js
View
@@ -64,10 +64,14 @@ Convert a valid date into a format as specified. Mostly conforms to [php.net's d
#### Arguments
-1. <var>**format**</var> (_string_) The format to convert the date to.
+1. <var>**format**</var> (_string_) The format to convert the date to. See the [date formatting table](#dateformatting).
+1. <var>**tzOffset**</var> (_number_) The timezone offset in minutes from GMT to display the date in.
+ * For example, if your server is running in PDT, but you want to display your dates in CDT, set this to 360.
+ * This can also be set globally via config variable `tzOffset` on [swig.init](getting-started.md#init).
+
<table style="width: 100%">
- <caption>Date formatting character information from <a href="http://php.net/date">php.net/date</a>.</caption>
+ <caption><a name="dateformatting"></a>Date formatting character information from <a href="http://php.net/date">php.net/date</a>.</caption>
<thead>
<tr>
<th>Format Character</th>
@@ -445,4 +449,4 @@ In your `myfilters.js` file, each filter method is just a simple javascript meth
return input.toString().toLowerCase();
};
-For more examples, view the [filters.js source file](../lib/filters.js)
+For more examples, view the [filters.js source file](../lib/filters.js)
View
@@ -12,7 +12,8 @@ In order to start using Swig, you should initialize it. Swig can be configured u
encoding: 'utf8',
filters: {},
root: '/',
- tags: {}
+ tags: {},
+ tzOffset: 0
});
This step is _optional_, however it is recommended to at least set the `root` key when running Swig from node.js.
@@ -51,6 +52,10 @@ The directory to search for templates. If a template passed to `swig.compileFile
Use this to set any custom tags and/or override any of the built-in tags. For more information on writing your own tags, see the [custom tags guide](custom-tags.md).
+#### tzOffset _optional_
+
+Sets a default timezone offset, in minutes from GMT. Setting this will make the [date filter](filters.md#date) automatically convert dates parsed through the date filter to the appropriate timezone offset.
+
Parsing a Template <a name="parsing" href="#parsing">#</a>
------------------
View
@@ -5,6 +5,7 @@ var fs = require('fs'),
parser = require('./lib/parser'),
filters = require('./lib/filters'),
helpers = require('./lib/helpers'),
+ dateformat = require('./lib/dateformat'),
_ = require('underscore'),
@@ -15,7 +16,8 @@ var fs = require('fs'),
encoding: 'utf8',
filters: filters,
root: '/',
- tags: tags
+ tags: tags,
+ tzOffset: 0
},
_config = _.extend({}, config),
CACHE = {};
@@ -26,6 +28,8 @@ exports.init = function (options) {
_config = _.extend({}, config, options);
_config.filters = _.extend(filters, options.filters);
_config.tags = _.extend(tags, options.tags);
+
+ dateformat.defaultTZOffset = _config.tzOffset;
};
function TemplateError(error) {
View
@@ -1,4 +1,5 @@
-var _months = {
+var _ = require('underscore'),
+ _months = {
full: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
},
@@ -8,6 +9,95 @@ var _months = {
alt: {'-1': 'Yesterday', 0: 'Today', 1: 'Tomorrow'}
};
+/*
+DateZ is licensed under the MIT License:
+Copyright (c) 2011 Tomo Universalis (http://tomouniversalis.com)
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+exports.defaultTZOffset = 0;
+exports.DateZ = function () {
+ var members = {
+ 'default': ['getUTCDate', 'getUTCDay', 'getUTCFullYear', 'getUTCHours', 'getUTCMilliseconds', 'getUTCMinutes', 'getUTCMonth', 'getUTCSeconds', 'toISOString', 'toGMTString', 'toUTCString', 'valueOf', 'getTime'],
+ z: ['getDate', 'getDay', 'getFullYear', 'getHours', 'getMilliseconds', 'getMinutes', 'getMonth', 'getSeconds', 'getYear', 'toDateString', 'toLocaleDateString', 'toLocaleTimeString'],
+ 'string': ['toLocaleString', 'toString', 'toTimeString'],
+ zSet: ['setDate', 'setFullYear', 'setHours', 'setMilliseconds', 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setYear'],
+ set: ['setUTCDate', 'setUTCFullYear', 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds'],
+ 'static': ['UTC', 'parse']
+ },
+ d = this,
+ i;
+
+ d.date = d.dateZ = (arguments.length > 1) ? new Date(Date.UTC.apply(Date, arguments) + ((new Date()).getTimezoneOffset() * 60000)) : (arguments.length === 1) ? new Date(new Date(arguments['0'])) : new Date();
+
+ d.timezoneOffset = d.dateZ.getTimezoneOffset();
+
+ function zeroPad(i) {
+ return (i < 10) ? '0' + i : i;
+ }
+ function _toTZString() {
+ var hours = zeroPad(Math.floor(Math.abs(d.timezoneOffset) / 60)),
+ minutes = zeroPad(Math.abs(d.timezoneOffset) - hours * 60),
+ prefix = (d.timezoneOffset < 0) ? '+' : '-',
+ abbr = (d.tzAbbreviation === undefined) ? '' : ' (' + d.tzAbbreviation + ')';
+
+ return 'GMT' + prefix + hours + minutes + abbr;
+ }
+
+ _.each(members.z, function (name) {
+ d[name] = function () {
+ return d.dateZ[name]();
+ };
+ });
+ _.each(members.string, function (name) {
+ d[name] = function () {
+ return d.dateZ[name].apply(d.dateZ, []).replace(/GMT[+\-]\\d{4} \\(([a-zA-Z]{3,4})\\)/, _toTZString());
+ };
+ });
+ _.each(members['default'], function (name) {
+ d[name] = function () {
+ return d.date[name]();
+ };
+ });
+ _.each(members['static'], function (name) {
+ d[name] = function () {
+ return Date[name].apply(Date, arguments);
+ };
+ });
+ _.each(members.zSet, function (name) {
+ d[name] = function () {
+ d.dateZ[name].apply(d.dateZ, arguments);
+ d.date = new Date(d.dateZ.getTime() - d.dateZ.getTimezoneOffset() * 60000 + d.timezoneOffset * 60000);
+ return d;
+ };
+ });
+ _.each(members.set, function (name) {
+ d[name] = function () {
+ d.date[name].apply(d.date, arguments);
+ d.dateZ = new Date(d.date.getTime() + d.date.getTimezoneOffset() * 60000 - d.timezoneOffset * 60000);
+ return d;
+ };
+ });
+
+ if (exports.defaultTZOffset) {
+ this.setTimezoneOffset(exports.defaultTZOffset);
+ }
+};
+exports.DateZ.prototype = {
+ getTimezoneOffset: function () {
+ return this.timezoneOffset;
+ },
+ setTimezoneOffset: function (offset, abbr) {
+ this.timezoneOffset = offset;
+ if (abbr) {
+ this.tzAbbreviation = abbr;
+ }
+ this.dateZ = new Date(this.date.getTime() + this.date.getTimezoneOffset() * 60000 - this.timezoneOffset * 60000);
+ return this;
+ }
+};
+
// Day
exports.d = function (input) {
return (input.getDate() < 10 ? '0' : '') + input.getDate();
@@ -31,9 +121,14 @@ exports.S = function (input) {
exports.w = function (input) {
return input.getDay() - 1;
};
-exports.z = function (input) {
- var year = input.getFullYear();
- return Math.round(((new Date(year, input.getMonth(), input.getDate(), 12, 0, 0)) - (new Date(year, 0, 1, 12, 0, 0))) / 86400000);
+exports.z = function (input, offset, abbr) {
+ var year = input.getFullYear(),
+ e = new exports.DateZ(year, input.getMonth(), input.getDate(), 12, 0, 0),
+ d = new exports.DateZ(year, 0, 1, 12, 0, 0);
+
+ e.setTimezoneOffset(offset, abbr);
+ d.setTimezoneOffset(offset, abbr);
+ return Math.round((e - d) / 86400000);
};
// Week
View
@@ -38,17 +38,21 @@ exports.capitalize = function (input) {
return input.toString().charAt(0).toUpperCase() + input.toString().substr(1).toLowerCase();
};
-exports.date = function (input, format) {
+exports.date = function (input, format, offset, abbr) {
var l = format.length,
- date = new Date(input),
+ date = new dateformat.DateZ(input),
cur,
i = 0,
out = '';
+ if (offset) {
+ date.setTimezoneOffset(offset, abbr);
+ }
+
for (i; i < l; i += 1) {
cur = format.charAt(i);
if (dateformat.hasOwnProperty(cur)) {
- out += dateformat[cur](date);
+ out += dateformat[cur](date, offset, abbr);
} else {
out += cur;
}
View
@@ -1,6 +1,7 @@
-var swig = require('../index');
+var swig = require('../index'),
+ DateZ = require('../lib/dateformat').DateZ;
-swig.init({});
+swig.init({ allowErrors: true });
function testFilter(test, filter, input, output, message) {
var tpl = swig.compile('{{ v|' + filter + ' }}');
@@ -33,14 +34,26 @@ exports.capitalize = function (test) {
test.done();
};
-exports.date = function (test) {
- var date = new Date(2011, 8, 6, 9, 5, 2),
- tpl = swig.compile('{{ d|date("d") }}');
+// Create dates for a particular timezone offset.
+// For example, specifying 480 (offset in minutes) for tzOffset creates a date
+// in your local timezone that is the same date as the specified y,m,d,h,i,s in PST.
+function makeDate(tzOffset, y, m, d, h, i, s) {
+ var date = new Date(y, m || 0, d || 0, h || 0, i || 0, s || 0),
+ offset = date.getTimezoneOffset();
+
+ if (offset !== tzOffset) { // timezone offset in PST for september
+ date = new Date(date.getTime() - ((offset * 60000) - (tzOffset * 60000)));
+ }
- date.setUTCHours(16);
+ return date;
+}
+
+exports.date = function (test) {
+ var date = makeDate(420, 2011, 8, 6, 9, 5, 2),
+ tpl = swig.compile('{{ d|date("d", 420) }}');
function testFormat(format, expected) {
- testFilter(test, 'date("' + format + '")', { v: date }, expected);
+ testFilter(test, 'date("' + format + '", 420)', { v: date }, expected, format);
}
// Day
testFormat('d', '06');
@@ -51,11 +64,14 @@ exports.date = function (test) {
testFormat('S', 'th');
testFormat('w', '1');
testFormat('z', '248');
- testFilter(test, 'date("z")', { v: new Date(2011, 0, 1) }, '0');
- testFilter(test, 'date("z")', { v: new Date(2011, 11, 31) }, '364');
+ testFilter(test, 'date("z", 480)', { v: makeDate(480, 2011, 0, 1) }, '0', 'z');
+ testFilter(test, 'date("z", 480)', { v: makeDate(480, 2011, 11, 31) }, '364', 'z');
// Week
- testFormat('W', '36');
+ if (date.getTimezoneOffset() > -400) {
+ // guaranteed to fail with the current test date in any timezone offset east of -400
+ testFormat('W', '36');
+ }
// Month
testFormat('F', 'September');
@@ -66,9 +82,9 @@ exports.date = function (test) {
// Year
testFormat('L', 'false');
- testFilter(test, 'date("L")', { v: new Date(2008, 1, 29) }, 'true');
+ testFilter(test, 'date("L", 480)', { v: makeDate(480, 2008, 1, 29) }, 'true', 'L');
testFormat('o', '2011');
- testFilter(test, 'date("o")', { v: new Date(2011, 0, 1) }, '2010');
+ testFilter(test, 'date("o", 480)', { v: makeDate(480, 2011, 0, 1) }, '2010', 'o');
testFormat('Y', '2011');
testFormat('y', '11');
@@ -79,16 +95,17 @@ exports.date = function (test) {
testFormat('g', '9');
testFormat('G', '9');
testFormat('h', '09');
- testFilter(test, 'date("h")', { v: new Date(2011, 0, 1, 10) }, '10');
+ testFilter(test, 'date("h", 480)', { v: makeDate(480, 2011, 0, 1, 10) }, '10', 'h');
testFormat('H', '09');
testFormat('i', '05');
testFormat('s', '02');
testFormat('d-m-Y', '06-09-2011');
- // These tests will fail in any other timezone. It's a bit hard to fake that out without directly implementing the methods inline.
// Timezone
testFormat('O', '+0700');
testFormat('Z', '25200');
+ testFilter(test, 'date("O", 360)', { v: date }, '+0600', 'O offset');
+ testFilter(test, 'date("G", 320)', { v: date }, '10', 'G offset');
// Full Date/Time
testFormat('c', '2011-09-06T16:05:02.000Z');
@@ -98,6 +115,29 @@ exports.date = function (test) {
test.done();
};
+exports['date with global offset'] = function (test) {
+ swig.init({
+ allowErrors: true,
+ tzOffset: 360
+ });
+
+ var tpl = swig.compile('{{ d|date("H:i:s") }}'),
+ date = new Date(2011, 8, 6, 9, 20, 10),
+ tzOffset = 480,
+ offset = date.getTimezoneOffset();
+
+ // make the date in PDT timezone
+ if (offset !== tzOffset) {
+ date = new Date(date.getTime() - ((offset * 60000) - (tzOffset * 60000)));
+ }
+
+ // date should return as same time in CDT, relative to PDT
+ test.strictEqual('11:20:10', swig.compile('{{ d|date("H:i:s") }}')({ d: date }), 'uses config value 360');
+ test.strictEqual('12:20:10', swig.compile('{{ d|date("H:i:s", 300) }}')({ d: date }), 'uses override 300');
+ test.strictEqual('20:40:10', swig.compile('{{ d|date("H:i:s", -200) }}')({ d: date }), 'uses override -200');
+ test.done();
+};
+
exports['default'] = function (test) {
testFilter(test, 'default("blah")', { v: 'foo' }, 'foo', 'string not overridden by default');
testFilter(test, 'default("blah")', { v: 0 }, '0', 'zero not overridden by default');

1 comment on commit 4ff5223

@gabrielfalcao

This comment has been minimized.

Show comment Hide comment
@gabrielfalcao

gabrielfalcao Dec 31, 2011

You're the man!

You're the man!

Please sign in to comment.