From 8d64d2ad93a8ee0cc7f63059eb4d1831f20a0bca Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Mon, 13 Feb 2017 12:21:13 -0600 Subject: [PATCH 1/6] Extracted the checking for timezone offset --- lib/index.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 6f67e79..d05eed2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -547,7 +547,7 @@ Utils.parseDateTimeField = function(field, options) { if (SugarDate.isValid(parsedDateWithTimezone)) { Utils.Logger.debug('Utils.parseDateTimeField: parsedDateWithTimezone:', parsedDateWithTimezone.toISOString()); - var hasTZOffset = new RegExp('[zZ]|[+-][01]\d:?[0-5]\d').test(field); + var hasTZOffset = Utils.hasTZOffset(field); // Convert to string. Important to remove offset/tz info (if none was provided originally) // or moment will ignore the passed in tz in later step. @@ -566,6 +566,21 @@ Utils.parseDateTimeField = function(field, options) { return rtnObject; }; + +/** + * Utils.hasTZOffset - Checks to see if the string has a timezone offset + * Examples + * - `-06:00` + * - `+0600` + * - `Z` + * + * @param {String} str The date string to be checked + * @return {Boolean} true or false + */ +Utils.hasTZOffset = function(str) { + return new RegExp('[zZ]|[+-][01]\d:?[0-5]\d').test(str); +}; + /** * Parses a boolean-ish input into a boolean object. * From 8092b5f895566035f253dc081046553c13641700 Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Mon, 13 Feb 2017 15:17:01 -0600 Subject: [PATCH 2/6] Extracted the logic that parses a date in a timezone into Utils.applyTzOffset --- lib/index.js | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/lib/index.js b/lib/index.js index d05eed2..edf4321 100644 --- a/lib/index.js +++ b/lib/index.js @@ -545,20 +545,8 @@ Utils.parseDateTimeField = function(field, options) { var parsedDateWithTimezone = parseDateTime(field, options, timezone); if (SugarDate.isValid(parsedDateWithTimezone)) { - Utils.Logger.debug('Utils.parseDateTimeField: parsedDateWithTimezone:', parsedDateWithTimezone.toISOString()); - var hasTZOffset = Utils.hasTZOffset(field); - - // Convert to string. Important to remove offset/tz info (if none was provided originally) - // or moment will ignore the passed in tz in later step. - var tzFormatStr = hasTZOffset ? '{Z}' : ''; - var dateString = SugarDate.format(parsedDateWithTimezone, '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{SSS}' + tzFormatStr); - Utils.Logger.debug('Utils.parseDateTimeField: dateString:', dateString); - - // This parses the dateString in the timezone specified. - var dateMoment = moment.tz(dateString, timezone); - Utils.Logger.debug('Utils.parseDateTimeField: moment:', dateMoment); - + var dateMoment = Utils.applyTzOffset(parsedDateWithTimezone, timezone, hasTZOffset); // Add the dateMoment to the return object rtnObject.moment = dateMoment; } @@ -567,6 +555,30 @@ Utils.parseDateTimeField = function(field, options) { }; +/** + * Utils.applyTzOffset - create a Moment.js date in a timezone + * + * @param {Date} date The date to create in the timezone + * @param {String} timezone Timezone to create the date into ex, 'America/New_York' + * @param {Boolean} hasTZOffset Specify if the incoming date had a offset already applied. + * @return {Moment} A moment.js object + */ +Utils.applyTzOffset = function(date, timezone, hasTZOffset) { + Utils.Logger.debug('Utils.applyTzOffset: date:', date.toISOString()); + + // Convert to string. Important to remove offset/tz info (if none was provided originally) + // or moment will ignore the passed in tz in later step. + var tzFormatStr = hasTZOffset ? '{Z}' : ''; + var sugarDateString = SugarDate.format(date, '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}.{SSS}' + tzFormatStr); + Utils.Logger.debug('Utils.applyTzOffset: sugarDateString:', sugarDateString); + + // This parses the dateString in the timezone specified. + var dateMoment = moment.tz(sugarDateString, timezone); + Utils.Logger.debug('Utils.applyTzOffset: moment:', dateMoment); + + return dateMoment; +}; + /** * Utils.hasTZOffset - Checks to see if the string has a timezone offset * Examples @@ -578,7 +590,7 @@ Utils.parseDateTimeField = function(field, options) { * @return {Boolean} true or false */ Utils.hasTZOffset = function(str) { - return new RegExp('[zZ]|[+-][01]\d:?[0-5]\d').test(str); + return /[zZ]|[+-][01]\d:?[0-5]\d/.test(str); }; /** From 79cc1b155e22be15132b3fdebcb76ad8aadbb3eb Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Tue, 14 Feb 2017 12:30:33 +0000 Subject: [PATCH 3/6] Split the functionality even furthar into dateToTimezone and applyTzOffset --- lib/index.js | 25 ++++++++++++++++++++----- spec/specs.js | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/index.js b/lib/index.js index edf4321..76ee506 100644 --- a/lib/index.js +++ b/lib/index.js @@ -546,7 +546,7 @@ Utils.parseDateTimeField = function(field, options) { var parsedDateWithTimezone = parseDateTime(field, options, timezone); if (SugarDate.isValid(parsedDateWithTimezone)) { var hasTZOffset = Utils.hasTZOffset(field); - var dateMoment = Utils.applyTzOffset(parsedDateWithTimezone, timezone, hasTZOffset); + var dateMoment = Utils.dateToTimezone(parsedDateWithTimezone, timezone, hasTZOffset); // Add the dateMoment to the return object rtnObject.moment = dateMoment; } @@ -554,17 +554,16 @@ Utils.parseDateTimeField = function(field, options) { return rtnObject; }; - /** - * Utils.applyTzOffset - create a Moment.js date in a timezone + * Utils.dateToTimezone - create a Moment.js date in a timezone * * @param {Date} date The date to create in the timezone * @param {String} timezone Timezone to create the date into ex, 'America/New_York' * @param {Boolean} hasTZOffset Specify if the incoming date had a offset already applied. * @return {Moment} A moment.js object */ -Utils.applyTzOffset = function(date, timezone, hasTZOffset) { - Utils.Logger.debug('Utils.applyTzOffset: date:', date.toISOString()); +Utils.dateToTimezone = function(date, timezone, hasTZOffset) { + Utils.Logger.debug('Utils.dateToTimezone: date:', date.toISOString()); // Convert to string. Important to remove offset/tz info (if none was provided originally) // or moment will ignore the passed in tz in later step. @@ -579,6 +578,22 @@ Utils.applyTzOffset = function(date, timezone, hasTZOffset) { return dateMoment; }; +/** + * Utils.applyTzOffset - Applies TZ Offset + * + * @param {Date} date The date to offset + * @param {String} timezone Timezone to offset the date for ex, 'America/New_York' + * @return {Date} A date with the offset applied + */ +Utils.applyTzOffset = function(date, timezone) { + Utils.Logger.debug('Utils.applyTzOffset: before:', date); + + SugarDate.addMinutes(date, moment.tz.zone(timezone).offset(date)); + + Utils.Logger.debug('Utils.applyTzOffset: after:', date); + return date; +}; + /** * Utils.hasTZOffset - Checks to see if the string has a timezone offset * Examples diff --git a/spec/specs.js b/spec/specs.js index bd16fec..311cc2d 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -1169,6 +1169,27 @@ describe('Utils', function() { }); }); + describe('Apply Timezone Offset', function() { + it('should return a date in the correct offset', function() { + var d; + + d = Utils.applyTzOffset(new Date('2017-02-14'), 'America/Chicago'); + expect(d).toEqual(moment.tz('2017-02-14', 'America/Chicago').toDate()); + expect(d.toISOString()).toEqual('2017-02-14T06:00:00.000Z'); + + d = Utils.applyTzOffset(new Date('2017-02-15'), 'America/New_York'); + expect(d).toEqual(moment.tz('2017-02-15', 'America/New_York').toDate()); + expect(d.toISOString()).toEqual('2017-02-15T05:00:00.000Z'); + + d = Utils.applyTzOffset(new Date('2017-02-16'), 'Europe/London'); + expect(d).toEqual(moment.tz('2017-02-16', 'Europe/London').toDate()); + expect(d.toISOString()).toEqual('2017-02-16T00:00:00.000Z'); + + d = Utils.applyTzOffset(new Date(), 'Europe/London'); + expect(d).toEqual(moment().tz('Europe/London').toDate()); + }); + }); + describe('Parse Boolean Field', function() { var expectTrue = function(input) { expect(Utils.parseBooleanField(input)).toEqual({ From f55d09c2cac8a7b548862b984353f7448bd2610b Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Tue, 14 Feb 2017 06:44:04 -0600 Subject: [PATCH 4/6] Fixed most of the tests when running them from a machine in timezone Europe/London --- spec/specs.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/spec/specs.js b/spec/specs.js index 311cc2d..bbf9349 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -809,7 +809,6 @@ describe('Utils', function() { it('should parse a datetime string', function() { expectValidDate('tomorrow', moment().add(1, 'days').startOf('day').toDate()); - expect(Utils._getFutureDate).toHaveBeenCalledWith('tomorrow', {locale: 'en'}); }); it('should return as invalid if no string is passed', function() { @@ -836,7 +835,6 @@ describe('Utils', function() { it('parses a date without locale', function() { var d = Utils.parseDateTimeField('now'); var expected = moment().toDate(); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en'}); // We give a margin of 100ms expectDatesToBeClose(d.parsed, expected); expectDatesToBeClose(d.moment.toDate(), expected); @@ -844,14 +842,12 @@ describe('Utils', function() { it('parses a date with locale', function() { var d = Utils.parseDateTimeField('11/1/2017', {locale: 'en-GB', timezone: 'UTC'}); - expect(Utils._getFutureDate).toHaveBeenCalledWith('11/1/2017', {locale: 'en-GB'}); expect(d.moment.toISOString()).toEqual('2017-01-11T00:00:00.000Z'); // .parsed is expected to be in the timezone of the host machine. expect(d.parsed).toEqual(moment('2017-01-11').toDate()); d = Utils.parseDateTimeField('11/1/2017', {locale: 'en', timezone: 'UTC'}); - expect(Utils._getFutureDate).toHaveBeenCalledWith('11/1/2017', {locale: 'en'}); expect(d.moment.toISOString()).toEqual('2017-11-01T00:00:00.000Z'); // .parsed is expected to be in the timezone of the host machine. @@ -861,7 +857,6 @@ describe('Utils', function() { it('defaults locale when timezone is Europe/London', function() { var d = Utils.parseDateTimeField('now', {timezone: 'Europe/London'}); var expected = moment().tz('Europe/London').toDate(); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en-GB'}); expectDatesToBeClose(d.moment.toDate(), expected); // .parsed is expected to be in the timezone of the host machine. @@ -871,8 +866,7 @@ describe('Utils', function() { describe('Timezones', function() { it('parses a date without timezone', function() { - var d = Utils.parseDateTimeField('1/11/2017'); - expect(Utils._getFutureDate).toHaveBeenCalledWith('1/11/2017', {locale: 'en'}); + var d = Utils.parseDateTimeField('2017-01-11'); expect(d.moment.toISOString()).toEqual(moment('2017-01-11').toISOString()); // .parsed is expected to be in the timezone of the host machine. @@ -881,7 +875,6 @@ describe('Utils', function() { it('parses a date with timezone', function() { var d = Utils.parseDateTimeField('1/11/2017', {timezone: 'America/New_York'}); - expect(Utils._getFutureDate).toHaveBeenCalledWith('1/11/2017', {locale: 'en'}); expect(d.moment.format()).toEqual('2017-01-11T00:00:00-05:00'); // .parsed is expected to be in the timezone of the host machine. @@ -942,7 +935,7 @@ describe('Utils', function() { describe('Offset Modifiers', function() { it('should strip offset modifier from string to parse', function() { Utils.parseDateTimeField('now +1d'); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en'}); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', jasmine.any(Object)); }); describe('Increment', function() { From a9f40de126b9ccfae85fc3c55fd8c0d111ed470a Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Tue, 14 Feb 2017 07:05:22 -0600 Subject: [PATCH 5/6] Fix tests to account for milliseconds when comparing two now dates. --- spec/specs.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/specs.js b/spec/specs.js index bbf9349..9a3c24f 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -1160,26 +1160,26 @@ describe('Utils', function() { }); }); }); - }); - describe('Apply Timezone Offset', function() { - it('should return a date in the correct offset', function() { - var d; + describe('Apply Timezone Offset', function() { + it('should return a date in the correct offset', function() { + var d; - d = Utils.applyTzOffset(new Date('2017-02-14'), 'America/Chicago'); - expect(d).toEqual(moment.tz('2017-02-14', 'America/Chicago').toDate()); - expect(d.toISOString()).toEqual('2017-02-14T06:00:00.000Z'); + d = Utils.applyTzOffset(new Date('2017-02-14'), 'America/Chicago'); + expect(d).toEqual(moment.tz('2017-02-14', 'America/Chicago').toDate()); + expect(d.toISOString()).toEqual('2017-02-14T06:00:00.000Z'); - d = Utils.applyTzOffset(new Date('2017-02-15'), 'America/New_York'); - expect(d).toEqual(moment.tz('2017-02-15', 'America/New_York').toDate()); - expect(d.toISOString()).toEqual('2017-02-15T05:00:00.000Z'); + d = Utils.applyTzOffset(new Date('2017-02-15'), 'America/New_York'); + expect(d).toEqual(moment.tz('2017-02-15', 'America/New_York').toDate()); + expect(d.toISOString()).toEqual('2017-02-15T05:00:00.000Z'); - d = Utils.applyTzOffset(new Date('2017-02-16'), 'Europe/London'); - expect(d).toEqual(moment.tz('2017-02-16', 'Europe/London').toDate()); - expect(d.toISOString()).toEqual('2017-02-16T00:00:00.000Z'); + d = Utils.applyTzOffset(new Date('2017-02-16'), 'Europe/London'); + expect(d).toEqual(moment.tz('2017-02-16', 'Europe/London').toDate()); + expect(d.toISOString()).toEqual('2017-02-16T00:00:00.000Z'); - d = Utils.applyTzOffset(new Date(), 'Europe/London'); - expect(d).toEqual(moment().tz('Europe/London').toDate()); + d = Utils.applyTzOffset(new Date(), 'Europe/London'); + expectDatesToBeClose(d, moment().tz('Europe/London').toDate()); + }); }); }); From 8942050517a6381f3c98b8730253e1b778afb409 Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Wed, 22 Feb 2017 13:07:34 -0600 Subject: [PATCH 6/6] Add workaround for SugarDate bug. Now `today at 3pm` actually works. --- lib/index.js | 6 +++++- spec/specs.js | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 76ee506..1f36040 100644 --- a/lib/index.js +++ b/lib/index.js @@ -39,9 +39,13 @@ Utils._getCurrentDate = function(options) { /* istanbul ignore next */ Utils._getFutureDate = function(str, options) { + // Work around bug in SugarDate + // if yesterday or today is in str `future:false` else true + var future = !/yesterday|today/.test(str); + // Makes unit testing possible, by allowing this // function to be mocked. - return SugarDate.create(str, _.assign({}, options, {future: true})); + return SugarDate.create(str, _.assign({}, options, {future: future})); }; Utils.activateDateParser = function() { diff --git a/spec/specs.js b/spec/specs.js index 9a3c24f..195ed42 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -908,9 +908,8 @@ describe('Utils', function() { checkTime(time, false, 'America/Chicago'); checkTime(time, false, 'UTC'); - // NOTE: Disable for now. There is a bug with SugarDate. - // checkTime(time, true, 'America/Chicago'); - // checkTime(time, true, 'UTC'); + checkTime(time, true, 'America/Chicago'); + checkTime(time, true, 'UTC'); }); });