diff --git a/lib/index.js b/lib/index.js index 66000db..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() { @@ -545,20 +549,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 = /[zZ]|[+-][01]\d:?[0-5]\d/.test(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 hasTZOffset = Utils.hasTZOffset(field); + var dateMoment = Utils.dateToTimezone(parsedDateWithTimezone, timezone, hasTZOffset); // Add the dateMoment to the return object rtnObject.moment = dateMoment; } @@ -566,6 +558,60 @@ Utils.parseDateTimeField = function(field, options) { return rtnObject; }; +/** + * 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.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. + 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.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 + * - `-06:00` + * - `+0600` + * - `Z` + * + * @param {String} str The date string to be checked + * @return {Boolean} true or false + */ +Utils.hasTZOffset = function(str) { + return /[zZ]|[+-][01]\d:?[0-5]\d/.test(str); +}; + /** * Parses a boolean-ish input into a boolean object. * diff --git a/spec/specs.js b/spec/specs.js index bd16fec..195ed42 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. @@ -915,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'); }); }); @@ -942,7 +934,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() { @@ -1167,6 +1159,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'); + expectDatesToBeClose(d, moment().tz('Europe/London').toDate()); + }); + }); }); describe('Parse Boolean Field', function() {