From ac0f6a55ed8c883e62870843a803a73f08a13779 Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Wed, 25 Jan 2017 11:19:47 -0600 Subject: [PATCH 1/7] Upgrade sugar-date to 2. Remove need for extending Date object. --- lib/index.js | 27 ++++++--------------------- package.json | 2 +- spec/specs.js | 27 ++++++++++----------------- 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/lib/index.js b/lib/index.js index 0bf37d0..3da6d80 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,7 @@ 'use strict'; var _ = require('lodash'); +var SugarDate = require('sugar-date').Date; var Utils = {}; @@ -35,20 +36,7 @@ Utils._getCurrentDate = function() { Utils._getFutureDate = function(str) { // Makes unit testing possible, by allowing this // function to be mocked. - return Date.future(str); -}; - -Utils.activateDateParser = function() { - // Add support for the 'enhanced' date object. - // This augments the Date prototype with extra methods, - // and (importantly) allows use to use the Sugar.js - // date parsing algorithms. - // In an ideal world, we wouldn't augment the prototype, - // but our hands are ties if we want to use the date parsing - // feature that Sugar.js gives us. - // Perhaps one day this can be extracted and added as a - // moment.js plugin. - require('sugar-date'); + return SugarDate.create(str, {future: true}); }; /** @@ -426,15 +414,12 @@ Utils.parseDateTimeField = function(field) { var parsedDate; - // Ensure we have Date superpowers - Utils.activateDateParser(); - if(_.isDate(field)) { parsedDate = field; } else if(!_.isString(field)) { // Just create a date from the passed value. - parsedDate = Date.create(field); + parsedDate = SugarDate.create(field); } else { // Regex for parsing a offset modifier. @@ -485,15 +470,15 @@ Utils.parseDateTimeField = function(field) { Utils._getCurrentDate() : Utils._getFutureDate(withoutOffsetModifiers); - if(parsedDate.isValid() && hasOffsetModifier && offsetSecs) { + if(SugarDate.isValid(parsedDate) && hasOffsetModifier && offsetSecs) { // Apply the offset modifier. // If it is negative, it will subtract. - parsedDate.addSeconds(offsetSecs); + SugarDate.addSeconds(parsedDate, offsetSecs); } } // parsedDate will always be a date at this point. - return getRtnObject(parsedDate, parsedDate.isValid()); + return getRtnObject(parsedDate, SugarDate.isValid(parsedDate)); }; /** diff --git a/package.json b/package.json index 424140d..6563073 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,6 @@ "dependencies": { "bluebird": "^3.4.1", "lodash": "^4.14.2", - "sugar-date": "1.5.1" + "sugar-date": "^2.0.4" } } diff --git a/spec/specs.js b/spec/specs.js index 672c19e..1211f56 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -2,7 +2,8 @@ var util = require('util'), Utils = require('../lib/index.js'), - _ = require('lodash'); + _ = require('lodash'), + SugarDate = require('sugar-date').Date; describe('Utils', function() { it('should convert an array to a hashtable', function() { @@ -776,14 +777,14 @@ describe('Utils', function() { valid: false, parsed: jasmine.any(Date) }); - expect(parsedDate.parsed.isValid()).toBe(false); + expect(SugarDate.isValid(parsedDate.parsed)).toBe(false); }; beforeEach(function() { epoch = new Date(0); spyOn(Utils, '_getFutureDate').and.callFake(function(str) { - var parsed = Date.future(str); - return parsed.isValid() ? epoch : parsed; + var parsed = SugarDate.create(str, {future: true}); + return SugarDate.isValid(parsed) ? epoch : parsed; }); }); @@ -919,17 +920,17 @@ describe('Utils', function() { }); it('should not apply the offset modifier if it is 0', function() { - spyOn(epoch, 'addSeconds'); + spyOn(SugarDate, 'addSeconds'); Utils.parseDateTimeField('now'); - expect(epoch.addSeconds) + expect(SugarDate.addSeconds) .not.toHaveBeenCalled(); }); it('should not apply the offset modifier if the date is invalid', function() { - epoch = new Date('invalid'); - spyOn(epoch, 'addSeconds'); + // epoch = new Date('invalid'); + spyOn(SugarDate, 'addSeconds'); Utils.parseDateTimeField('some_invalid_date +40h'); - expect(epoch.addSeconds) + expect(SugarDate.addSeconds) .not.toHaveBeenCalled(); }); @@ -942,10 +943,6 @@ describe('Utils', function() { expectValidDate('2013-02-08 09:30-0100', epoch); }); - it('should work with the YYYY-MM-DD HHZZ format when the offset is Z', function() { - expectValidDate('2013-02-08 09Z', epoch); - }); - it('should work with the YYYY-MM-DD HH:mm:ss.SSSZ format', function() { expectValidDate('2013-02-08 09:30:26.123+07:00', epoch); }); @@ -966,10 +963,6 @@ describe('Utils', function() { expectValidDate('2013-02-08 09:30-0100 +1d - 12h', new Date(43200000)); }); - it('should work with the YYYY-MM-DD HHZZ format when the offset is Z and modifiers', function() { - expectValidDate('2013-02-08 09Z +1d - 12h', new Date(43200000)); - }); - it('should work with the YYYY-MM-DD HH:mm:ss.SSSZ format and modifiers', function() { expectValidDate('2013-02-08 09:30:26.123+07:00 +1d - 12h', new Date(43200000)); }); From aca7024dd3e483006251b78db1028802c7a85f59 Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Thu, 26 Jan 2017 05:04:53 -0600 Subject: [PATCH 2/7] Added back extending Date object as optional. Support legacy systems that depend on this lib --- lib/index.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/index.js b/lib/index.js index 3da6d80..23774ef 100644 --- a/lib/index.js +++ b/lib/index.js @@ -39,6 +39,17 @@ Utils._getFutureDate = function(str) { return SugarDate.create(str, {future: true}); }; +Utils.activateDateParser = function() { + // Add support for the 'enhanced' date object. + // This augments the Date prototype with extra methods, + // and (importantly) allows use to use the Sugar.js + // date parsing algorithms. + + // v2 of SugarDate supports optionally extending. + // This is still here to support legacy dependencies + SugarDate.extend(); +}; + /** * Converts an array into a hashtable, with each value set * to `true`. Useful for lookups, avoiding Array.indexOf calls. From 2f1769136bf7d1d7002682dff23f054d8e9eba7f Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Thu, 26 Jan 2017 12:05:07 -0600 Subject: [PATCH 3/7] Added support for locale --- lib/index.js | 17 +++++++++-------- spec/specs.js | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/index.js b/lib/index.js index 23774ef..294a455 100644 --- a/lib/index.js +++ b/lib/index.js @@ -2,6 +2,7 @@ var _ = require('lodash'); var SugarDate = require('sugar-date').Date; +require('sugar-date/locales'); var Utils = {}; @@ -26,17 +27,17 @@ var formatParsedObject = function(type, input, valid, parsed) { }; /* istanbul ignore next */ -Utils._getCurrentDate = function() { +Utils._getCurrentDate = function(locale) { // Makes unit testing possible, by allowing this // function to be mocked. - return new Date(); + return SugarDate.create('now', locale); }; /* istanbul ignore next */ -Utils._getFutureDate = function(str) { +Utils._getFutureDate = function(str, locale) { // Makes unit testing possible, by allowing this // function to be mocked. - return SugarDate.create(str, {future: true}); + return SugarDate.create(str, {future: true, locale: locale}); }; Utils.activateDateParser = function() { @@ -418,7 +419,7 @@ Utils.cloneTerse = function(input) { * @param {String} field the date string to parse. * @return {Object} an object containing the parsed date, or the passed field if it was not a String. */ -Utils.parseDateTimeField = function(field) { +Utils.parseDateTimeField = function(field, locale) { var getRtnObject = function(parsed, isValid) { return formatParsedObject('date', field, isValid, parsed); }; @@ -430,7 +431,7 @@ Utils.parseDateTimeField = function(field) { } else if(!_.isString(field)) { // Just create a date from the passed value. - parsedDate = SugarDate.create(field); + parsedDate = SugarDate.create(field, locale); } else { // Regex for parsing a offset modifier. @@ -478,8 +479,8 @@ Utils.parseDateTimeField = function(field) { // Otherwise, parse the string to create a date in the future. parsedDate = withoutOffsetModifiers === '' && hasOffsetModifier ? - Utils._getCurrentDate() : - Utils._getFutureDate(withoutOffsetModifiers); + Utils._getCurrentDate(locale) : + Utils._getFutureDate(withoutOffsetModifiers, locale); if(SugarDate.isValid(parsedDate) && hasOffsetModifier && offsetSecs) { // Apply the offset modifier. diff --git a/spec/specs.js b/spec/specs.js index 1211f56..70a9a0a 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -800,7 +800,7 @@ describe('Utils', function() { it('should parse a datetime string', function() { expectValidDate('tomorrow', epoch); - expect(Utils._getFutureDate).toHaveBeenCalledWith('tomorrow'); + expect(Utils._getFutureDate).toHaveBeenCalledWith('tomorrow', undefined); }); it('should return as invalid if no string is passed', function() { @@ -815,10 +815,40 @@ describe('Utils', function() { expectInvalidDate('invalid'); }); + it('should handle a integer', function() { + epoch = new Date(1485410400000); + expectValidDate(1485410400000, new Date(1485410400000)); + }); + + it('should handle a Date', function() { + epoch = new Date(1485410400000); + expectValidDate(new Date(1485410400000), new Date(1485410400000)); + }); + + describe('Locales', function() { + it('parses a date withtout locale', function() { + Utils.parseDateTimeField('now +1d'); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', undefined); + + epoch = SugarDate.create('1/11/2017', {fromUTC: true}); + var d = Utils.parseDateTimeField('1/11/2017'); + expect(d.parsed.toISOString()).toEqual('2017-01-11T00:00:00.000Z'); + }); + + it('parses a date with locale', function() { + Utils.parseDateTimeField('now +1d', 'en-GB'); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', 'en-GB'); + + epoch = SugarDate.create('1/11/2017', {fromUTC: true}); + var d = Utils.parseDateTimeField('11/1/2017'); + expect(d.parsed.toISOString()).toEqual('2017-01-11T00:00:00.000Z'); + }); + }); + describe('Offset Modifiers', function() { it('should strip offset modifier from string to parse', function() { Utils.parseDateTimeField('now +1d'); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now'); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', undefined); }); describe('Increment', function() { From a044b207fd5743200a700cfcf83a89b300607ae0 Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Tue, 31 Jan 2017 05:05:45 -0600 Subject: [PATCH 4/7] Added support for options as second param to parseDateTimeField --- lib/index.js | 24 +++++++++++++++++------- spec/specs.js | 8 ++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/lib/index.js b/lib/index.js index 294a455..8be448a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -27,17 +27,17 @@ var formatParsedObject = function(type, input, valid, parsed) { }; /* istanbul ignore next */ -Utils._getCurrentDate = function(locale) { +Utils._getCurrentDate = function(options) { // Makes unit testing possible, by allowing this // function to be mocked. - return SugarDate.create('now', locale); + return SugarDate.create('now', options); }; /* istanbul ignore next */ -Utils._getFutureDate = function(str, locale) { +Utils._getFutureDate = function(str, options) { // Makes unit testing possible, by allowing this // function to be mocked. - return SugarDate.create(str, {future: true, locale: locale}); + return SugarDate.create(str, _.assign({future: true}, options)); }; Utils.activateDateParser = function() { @@ -405,6 +405,8 @@ Utils.cloneTerse = function(input) { * - Utils.parseDateTimeField('may 25th of next year') * - Utils.parseDateTimeField('2014-01-18 09:30:00') * - Utils.parseDateTimeField('2014-01-18 09:30:00 -0400') + * - Utils.parseDateTimeField('2014-01-18 09:30:00', 'en-GB') + * - Utils.parseDateTimeField('2014-01-18 09:30:00', {fromUTC: true, setUTC: true}) * - Utils.parseDateTimeField('in 2 days') * - Utils.parseDateTimeField('5 minutes from now') * - Utils.parseDateTimeField('2014-01-18 09:30:00 -0400 +2d +30m') @@ -417,6 +419,7 @@ Utils.cloneTerse = function(input) { * - -4d -6h will reduce the parsed date by 4 days and 6 hours * * @param {String} field the date string to parse. + * @param {String} locale the locale string OR options object. * @return {Object} an object containing the parsed date, or the passed field if it was not a String. */ Utils.parseDateTimeField = function(field, locale) { @@ -424,6 +427,13 @@ Utils.parseDateTimeField = function(field, locale) { return formatParsedObject('date', field, isValid, parsed); }; + var options = {}; + if (_.isString(locale)) { + options.locale = locale; + } else if (locale) { + options = locale; + } + var parsedDate; if(_.isDate(field)) { @@ -431,7 +441,7 @@ Utils.parseDateTimeField = function(field, locale) { } else if(!_.isString(field)) { // Just create a date from the passed value. - parsedDate = SugarDate.create(field, locale); + parsedDate = SugarDate.create(field, options); } else { // Regex for parsing a offset modifier. @@ -479,8 +489,8 @@ Utils.parseDateTimeField = function(field, locale) { // Otherwise, parse the string to create a date in the future. parsedDate = withoutOffsetModifiers === '' && hasOffsetModifier ? - Utils._getCurrentDate(locale) : - Utils._getFutureDate(withoutOffsetModifiers, locale); + Utils._getCurrentDate(options) : + Utils._getFutureDate(withoutOffsetModifiers, options); if(SugarDate.isValid(parsedDate) && hasOffsetModifier && offsetSecs) { // Apply the offset modifier. diff --git a/spec/specs.js b/spec/specs.js index 70a9a0a..e1e64bf 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -800,7 +800,7 @@ describe('Utils', function() { it('should parse a datetime string', function() { expectValidDate('tomorrow', epoch); - expect(Utils._getFutureDate).toHaveBeenCalledWith('tomorrow', undefined); + expect(Utils._getFutureDate).toHaveBeenCalledWith('tomorrow', {}); }); it('should return as invalid if no string is passed', function() { @@ -828,7 +828,7 @@ describe('Utils', function() { describe('Locales', function() { it('parses a date withtout locale', function() { Utils.parseDateTimeField('now +1d'); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', undefined); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {}); epoch = SugarDate.create('1/11/2017', {fromUTC: true}); var d = Utils.parseDateTimeField('1/11/2017'); @@ -837,7 +837,7 @@ describe('Utils', function() { it('parses a date with locale', function() { Utils.parseDateTimeField('now +1d', 'en-GB'); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', 'en-GB'); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en-GB'}); epoch = SugarDate.create('1/11/2017', {fromUTC: true}); var d = Utils.parseDateTimeField('11/1/2017'); @@ -848,7 +848,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', undefined); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {}); }); describe('Increment', function() { From 194789ea06ba932218c3608ca735aebeaf65f191 Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Mon, 6 Feb 2017 16:04:19 -0600 Subject: [PATCH 5/7] Added newDateInternal option --- lib/index.js | 10 ++++++++++ spec/specs.js | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 8be448a..0609423 100644 --- a/lib/index.js +++ b/lib/index.js @@ -434,6 +434,12 @@ Utils.parseDateTimeField = function(field, locale) { options = locale; } + var previousNewDateInternal; + if (_.isFunction(options.newDateInternal)) { + previousNewDateInternal = SugarDate.getOption('newDateInternal'); + SugarDate.setOption('newDateInternal', options.newDateInternal); + } + var parsedDate; if(_.isDate(field)) { @@ -499,6 +505,10 @@ Utils.parseDateTimeField = function(field, locale) { } } + if (previousNewDateInternal) { + SugarDate.setOption('newDateInternal', previousNewDateInternal); + } + // parsedDate will always be a date at this point. return getRtnObject(parsedDate, SugarDate.isValid(parsedDate)); }; diff --git a/spec/specs.js b/spec/specs.js index e1e64bf..d9aa4a8 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -825,8 +825,19 @@ describe('Utils', function() { expectValidDate(new Date(1485410400000), new Date(1485410400000)); }); + it('should support newDateInternal as an option', function() { + epoch = SugarDate.create('2017-02-06 1am', {fromUTC: true}); + var fn = function() { + var d = new Date(); + d.setTime(d.getTime() + (60 * 60 * 1000)); + return d; + }; + var dateObj = Utils.parseDateTimeField('2017-02-06', {newDateInternal: fn}); + expect(dateObj.parsed.toISOString()).toEqual('2017-02-06T01:00:00.000Z'); + }); + describe('Locales', function() { - it('parses a date withtout locale', function() { + it('parses a date without locale', function() { Utils.parseDateTimeField('now +1d'); expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {}); From 9af9c20ab2ff3beaa4037e09721b9cc18bea546a Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Tue, 7 Feb 2017 04:00:49 -0600 Subject: [PATCH 6/7] Changed second param to always expect an object. Fixed a potential bug with gettign the future date. --- lib/index.js | 16 +++++----------- spec/specs.js | 2 +- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/index.js b/lib/index.js index 0609423..9e20ec8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -37,7 +37,7 @@ Utils._getCurrentDate = function(options) { Utils._getFutureDate = function(str, options) { // Makes unit testing possible, by allowing this // function to be mocked. - return SugarDate.create(str, _.assign({future: true}, options)); + return SugarDate.create(str, _.assign({}, options, {future: true})); }; Utils.activateDateParser = function() { @@ -405,7 +405,7 @@ Utils.cloneTerse = function(input) { * - Utils.parseDateTimeField('may 25th of next year') * - Utils.parseDateTimeField('2014-01-18 09:30:00') * - Utils.parseDateTimeField('2014-01-18 09:30:00 -0400') - * - Utils.parseDateTimeField('2014-01-18 09:30:00', 'en-GB') + * - Utils.parseDateTimeField('2014-01-18 09:30:00', {locale: 'en-GB'}) * - Utils.parseDateTimeField('2014-01-18 09:30:00', {fromUTC: true, setUTC: true}) * - Utils.parseDateTimeField('in 2 days') * - Utils.parseDateTimeField('5 minutes from now') @@ -419,20 +419,14 @@ Utils.cloneTerse = function(input) { * - -4d -6h will reduce the parsed date by 4 days and 6 hours * * @param {String} field the date string to parse. - * @param {String} locale the locale string OR options object. + * @param {Object} options passed through to SugarDate.create * @return {Object} an object containing the parsed date, or the passed field if it was not a String. */ -Utils.parseDateTimeField = function(field, locale) { +Utils.parseDateTimeField = function(field, options) { var getRtnObject = function(parsed, isValid) { return formatParsedObject('date', field, isValid, parsed); }; - - var options = {}; - if (_.isString(locale)) { - options.locale = locale; - } else if (locale) { - options = locale; - } + options = options || {}; var previousNewDateInternal; if (_.isFunction(options.newDateInternal)) { diff --git a/spec/specs.js b/spec/specs.js index d9aa4a8..9b63fde 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -847,7 +847,7 @@ describe('Utils', function() { }); it('parses a date with locale', function() { - Utils.parseDateTimeField('now +1d', 'en-GB'); + Utils.parseDateTimeField('now +1d', {locale: 'en-GB'}); expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en-GB'}); epoch = SugarDate.create('1/11/2017', {fromUTC: true}); From c5568e57d4f223100d8efb264f2b74960c46b14b Mon Sep 17 00:00:00 2001 From: Sam Shepherd Date: Tue, 7 Feb 2017 16:26:30 -0600 Subject: [PATCH 7/7] Added timezone ability to parseDateTimeField. `moment` is now returned and is a timezone aware moment.js object. --- lib/index.js | 59 +++++++++++++++++++++++++++++++++++++++++++++------ package.json | 1 + spec/specs.js | 40 ++++++++++++++++++++++++++++------ 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/lib/index.js b/lib/index.js index 9e20ec8..247b6ec 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,17 @@ 'use strict'; -var _ = require('lodash'); -var SugarDate = require('sugar-date').Date; +var _ = require('lodash'), + moment = require('moment-timezone'), + SugarDate = require('sugar-date').Date; require('sugar-date/locales'); var Utils = {}; var DEFAULT_FLATTENED_DELIMITER = '__'; var DEFAULT_FLATTENED_ARRAY_DELIMITER = '_+_'; +var TIMEZONE_TO_LOCALE = { + 'Europe/London': 'en-GB' +}; var humanize = function(str) { /* istanbul ignore if */ @@ -406,6 +410,7 @@ Utils.cloneTerse = function(input) { * - Utils.parseDateTimeField('2014-01-18 09:30:00') * - Utils.parseDateTimeField('2014-01-18 09:30:00 -0400') * - Utils.parseDateTimeField('2014-01-18 09:30:00', {locale: 'en-GB'}) + * - Utils.parseDateTimeField('2014-01-18 09:30:00', {timezone: 'America/Chicago'}) * - Utils.parseDateTimeField('2014-01-18 09:30:00', {fromUTC: true, setUTC: true}) * - Utils.parseDateTimeField('in 2 days') * - Utils.parseDateTimeField('5 minutes from now') @@ -426,12 +431,33 @@ Utils.parseDateTimeField = function(field, options) { var getRtnObject = function(parsed, isValid) { return formatParsedObject('date', field, isValid, parsed); }; - options = options || {}; + // Copy options + options = _.assign({}, options); + + // Default timezone + var timezone = options.timezone || 'UTC'; + var isUTC = timezone.toLowerCase() === 'utc'; + + // Lookup Locale. Check options, then look up based on timezone. Default to 'en' + options.locale = options.locale || TIMEZONE_TO_LOCALE[timezone] || 'en'; + + // If invalid + if (moment.tz.zone(timezone) === null) { + Utils.Logger.warn('Utils.parseDateTimeField - Timezone "' + timezone + '" is invalid. Assuming UTC'); + timezone = 'UTC'; + } + + // This special case is here to get around a bug in SugarDate. Issue: #582 + options.fromUTC = field === 'now' ? false : isUTC; + + // Help SugarDate be aware of timezones var previousNewDateInternal; - if (_.isFunction(options.newDateInternal)) { + if (timezone) { previousNewDateInternal = SugarDate.getOption('newDateInternal'); - SugarDate.setOption('newDateInternal', options.newDateInternal); + SugarDate.setOption('newDateInternal', function() { + return moment().tz(timezone).toDate(); + }); } var parsedDate; @@ -503,8 +529,29 @@ Utils.parseDateTimeField = function(field, options) { SugarDate.setOption('newDateInternal', previousNewDateInternal); } + var dateMoment; + if (SugarDate.isValid(parsedDate)) { + var hasTZOffset = new RegExp('[+-]{1}[0-9]{2}:?[0-9]{2}').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(SugarDate.setUTC(parsedDate, isUTC), '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}' + tzFormatStr); + Utils.Logger.debug('Utils.parseDateTimeField: dateString:' + dateString); + + // This parses the dateString in the timezone specified. + dateMoment = moment.tz(dateString, timezone); + // Utils.Logger.debug('Utils.parseDateTimeField: moment:' + dateMoment); + } + // parsedDate will always be a date at this point. - return getRtnObject(parsedDate, SugarDate.isValid(parsedDate)); + var rtnObject = getRtnObject(parsedDate, SugarDate.isValid(parsedDate)); + + // Add the dateMoment to the return object + if (dateMoment) { + rtnObject.moment = dateMoment; + } + return rtnObject; }; /** diff --git a/package.json b/package.json index 6563073..7861e32 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "dependencies": { "bluebird": "^3.4.1", "lodash": "^4.14.2", + "moment-timezone": "^0.5.11", "sugar-date": "^2.0.4" } } diff --git a/spec/specs.js b/spec/specs.js index 9b63fde..e583656 100644 --- a/spec/specs.js +++ b/spec/specs.js @@ -765,7 +765,8 @@ describe('Utils', function() { type: 'date', input: input, valid: true, - parsed: expected + parsed: expected, + moment: jasmine.any(Object) }); }; @@ -782,6 +783,7 @@ describe('Utils', function() { beforeEach(function() { epoch = new Date(0); + // spyOn(Utils, '_getFutureDate').and.callThrough(); spyOn(Utils, '_getFutureDate').and.callFake(function(str) { var parsed = SugarDate.create(str, {future: true}); return SugarDate.isValid(parsed) ? epoch : parsed; @@ -800,7 +802,7 @@ describe('Utils', function() { it('should parse a datetime string', function() { expectValidDate('tomorrow', epoch); - expect(Utils._getFutureDate).toHaveBeenCalledWith('tomorrow', {}); + expect(Utils._getFutureDate).toHaveBeenCalledWith('tomorrow', {locale: 'en', fromUTC: true}); }); it('should return as invalid if no string is passed', function() { @@ -839,7 +841,7 @@ describe('Utils', function() { describe('Locales', function() { it('parses a date without locale', function() { Utils.parseDateTimeField('now +1d'); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {}); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en', fromUTC: true}); epoch = SugarDate.create('1/11/2017', {fromUTC: true}); var d = Utils.parseDateTimeField('1/11/2017'); @@ -848,18 +850,44 @@ describe('Utils', function() { it('parses a date with locale', function() { Utils.parseDateTimeField('now +1d', {locale: 'en-GB'}); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en-GB'}); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en-GB', fromUTC: true}); epoch = SugarDate.create('1/11/2017', {fromUTC: true}); var d = Utils.parseDateTimeField('11/1/2017'); expect(d.parsed.toISOString()).toEqual('2017-01-11T00:00:00.000Z'); }); + + }); + + describe('Timezones', function() { + it('parses a date without timezone', function() { + Utils.parseDateTimeField('1/11/2017 +1d'); + expect(Utils._getFutureDate).toHaveBeenCalledWith('1/11/2017', {locale: 'en', fromUTC: true}); + + epoch = SugarDate.create('1/12/2017', {fromUTC: true}); + var d = Utils.parseDateTimeField('1/12/2017'); + expect(d.moment.toISOString()).toEqual('2017-01-12T00:00:00.000Z'); + }); + + it('parses a date with timezone', function() { + Utils.parseDateTimeField('1/11/2017 +1d', {timezone: 'America/New_York'}); + expect(Utils._getFutureDate).toHaveBeenCalledWith('1/11/2017', { + locale: 'en', + timezone: 'America/New_York', + fromUTC: false + }); + + epoch = SugarDate.create('1/12/2017'); + var d = Utils.parseDateTimeField('1/12/2017', {timezone: 'America/New_York'}); + expect(d.moment.toISOString()).toEqual('2017-01-12T05:00:00.000Z'); + }); + }); describe('Offset Modifiers', function() { it('should strip offset modifier from string to parse', function() { Utils.parseDateTimeField('now +1d'); - expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {}); + expect(Utils._getFutureDate).toHaveBeenCalledWith('now', {locale: 'en', fromUTC: true}); }); describe('Increment', function() { @@ -962,7 +990,7 @@ describe('Utils', function() { it('should not apply the offset modifier if it is 0', function() { spyOn(SugarDate, 'addSeconds'); - Utils.parseDateTimeField('now'); + // console.log(Utils.parseDateTimeField('now')); expect(SugarDate.addSeconds) .not.toHaveBeenCalled(); });