Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bugs with parsing to and from timezones #7

Merged
merged 3 commits into from
Feb 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 103 additions & 91 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,125 +432,137 @@ Utils.parseDateTimeField = function(field, options) {
return formatParsedObject('date', field, isValid, parsed);
};

Utils.Logger.debug('Utils.parseDateTimeField: params:', field, 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';
var timezone = options.timezone || moment.tz.guess();
delete options.timezone;

// If invalid
if (moment.tz.zone(timezone) === null) {
Utils.Logger.warn('Utils.parseDateTimeField - Timezone "' + timezone + '" is invalid. Assuming UTC');
timezone = 'UTC';
var tz = moment.tz.guess() || 'UTC';
Utils.Logger.warn('Utils.parseDateTimeField - Timezone "' + timezone + '" is invalid. Assuming ' + tz);
timezone = tz;
}

// 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 (timezone) {
previousNewDateInternal = SugarDate.getOption('newDateInternal');
SugarDate.setOption('newDateInternal', function() {
return moment().tz(timezone).toDate();
});
}
// Lookup Locale. Check options, then look up based on timezone. Default to 'en'
options.locale = options.locale || TIMEZONE_TO_LOCALE[timezone] || 'en';

var parsedDate;
function parseDateTime(field, options, timezone) {
Utils.Logger.debug('Utils.parseDateTimeField: parseDateTime:', field, options, timezone);
// Help SugarDate be aware of timezones
var previousNewDateInternal;
if (timezone) {
Utils.Logger.debug('Utils.parseDateTimeField: parseDateTime: Setting up newDateInternal');
previousNewDateInternal = SugarDate.getOption('newDateInternal');
SugarDate.setOption('newDateInternal', function() {
var date = new Date();
var offsetMinutes = date.getTimezoneOffset() - moment.tz.zone(timezone).offset(date);
SugarDate.addMinutes(date, offsetMinutes);
Utils.Logger.debug('Utils.parseDateTimeField: newDateInternal:', offsetMinutes, date);
return date;
});
}

if(_.isDate(field)) {
parsedDate = field;
var parsedDate;

} else if(!_.isString(field)) {
// Just create a date from the passed value.
parsedDate = SugarDate.create(field, options);
if(_.isDate(field)) {
parsedDate = field;

} else {
// Regex for parsing a offset modifier.
var offsetRegex = /(?:\s*)([+-])(?:\s*)(\d+)([dhms])/gi;
} else if(!_.isString(field)) {
// Just create a date from the passed value.
parsedDate = SugarDate.create(field, options);

// Multipliers, for converting the type of offset
// modifier into seconds.
var offsetMultipliers = {
s: 1, m: 60, h: 3600, d: 86400
};
} else {
// Regex for parsing a offset modifier.
var offsetRegex = /(?:\s*)([+-])(?:\s*)(\d+)([dhms])/gi;

// Multipliers, for converting the type of offset
// modifier into seconds.
var offsetMultipliers = {
s: 1, m: 60, h: 3600, d: 86400
};

// Run the regex on the string to strip out any
// offset modifiers, adding/subtracting
// them from the overall offsetSecs.
var offsetSecs = 0;
var hasOffsetModifier = false;

var withoutOffsetModifiers = field
.replace(
offsetRegex,
function(match, p1, p2, p3) {
if(p1 === '+') {
// We should increment the offsetSecs
// by the appropriate amount.
offsetSecs = offsetSecs + (offsetMultipliers[p3] * p2);

} else {
// We should decrement the offsetSecs
// by the appropriate amount.
offsetSecs = offsetSecs - (offsetMultipliers[p3] * p2);
}

// Mark the fact we have at least one offset modifier.
hasOffsetModifier = true;

// Return a blank string, to remove the
// offset modifier from the original string.
// This is so that the offset modifier does
// not interfere with the datetime parsing.
return '';
})
.trim();

// If we only have offset modifiers, initialise the date as now.
// Otherwise, parse the string to create a date in the future.
parsedDate =
withoutOffsetModifiers === '' && hasOffsetModifier ?
Utils._getCurrentDate(options) :
Utils._getFutureDate(withoutOffsetModifiers, options);

if(SugarDate.isValid(parsedDate) && hasOffsetModifier && offsetSecs) {
// Apply the offset modifier.
// If it is negative, it will subtract.
SugarDate.addSeconds(parsedDate, offsetSecs);
}
}

// Run the regex on the string to strip out any
// offset modifiers, adding/subtracting
// them from the overall offsetSecs.
var offsetSecs = 0;
var hasOffsetModifier = false;

var withoutOffsetModifiers = field
.replace(
offsetRegex,
function(match, p1, p2, p3) {
if(p1 === '+') {
// We should increment the offsetSecs
// by the appropriate amount.
offsetSecs = offsetSecs + (offsetMultipliers[p3] * p2);

} else {
// We should decrement the offsetSecs
// by the appropriate amount.
offsetSecs = offsetSecs - (offsetMultipliers[p3] * p2);
}

// Mark the fact we have at least one offset modifier.
hasOffsetModifier = true;

// Return a blank string, to remove the
// offset modifier from the original string.
// This is so that the offset modifier does
// not interfere with the datetime parsing.
return '';
})
.trim();

// If we only have offset modifiers, initialise the date as now.
// Otherwise, parse the string to create a date in the future.
parsedDate =
withoutOffsetModifiers === '' && hasOffsetModifier ?
Utils._getCurrentDate(options) :
Utils._getFutureDate(withoutOffsetModifiers, options);

if(SugarDate.isValid(parsedDate) && hasOffsetModifier && offsetSecs) {
// Apply the offset modifier.
// If it is negative, it will subtract.
SugarDate.addSeconds(parsedDate, offsetSecs);
if (previousNewDateInternal) {
Utils.Logger.debug('Utils.parseDateTimeField: parseDateTime: Tearing down newDateInternal');
SugarDate.setOption('newDateInternal', previousNewDateInternal);
}
}

if (previousNewDateInternal) {
SugarDate.setOption('newDateInternal', previousNewDateInternal);
return parsedDate;
}

var dateMoment;
if (SugarDate.isValid(parsedDate)) {
var hasTZOffset = new RegExp('[+-]{1}[0-9]{2}:?[0-9]{2}').test(field);
var parsedDate = parseDateTime(field, options);
var rtnObject = getRtnObject(parsedDate, SugarDate.isValid(parsedDate));

var parsedDateWithTimezone = parseDateTime(field, options, timezone);
if (SugarDate.isValid(parsedDateWithTimezone)) {
Utils.Logger.debug('Utils.parseDateTimeField: parsedDateWithTimezone:', parsedDateWithTimezone.toISOString());

var hasTZOffset = new RegExp('[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(SugarDate.setUTC(parsedDate, isUTC), '{yyyy}-{MM}-{dd}T{HH}:{mm}:{ss}' + tzFormatStr);
Utils.Logger.debug('Utils.parseDateTimeField: dateString:' + dateString);
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.
dateMoment = moment.tz(dateString, timezone);
// Utils.Logger.debug('Utils.parseDateTimeField: moment:' + dateMoment);
}
var dateMoment = moment.tz(dateString, timezone);
Utils.Logger.debug('Utils.parseDateTimeField: moment:', dateMoment);

// parsedDate will always be a date at this point.
var rtnObject = getRtnObject(parsedDate, SugarDate.isValid(parsedDate));

// Add the dateMoment to the return object
if (dateMoment) {
// Add the dateMoment to the return object
rtnObject.moment = dateMoment;
}

return rtnObject;
};

Expand Down
5 changes: 3 additions & 2 deletions lib/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
// It is a simple wrapper around console.log, that logs everything out.

var log = function() {
console.log.apply(console, arguments);
exports.on && console.log.apply(console, arguments);
};

exports.log = function() {
// Shift the first argument, which is 'info', 'debug' etc.
log.apply(null, Array.prototype.slice.call(arguments, 1));
exports.on && log.apply(null, Array.prototype.slice.call(arguments, 1));
};

exports.silly = log;
Expand All @@ -20,3 +20,4 @@ exports.verbose = log;
exports.info = log;
exports.warn = log;
exports.error = log;
exports.on = false;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"jasmine-spec-reporter": "2.5.0",
"jshint": "2.9.2",
"jshint-stylish": "2.2.0",
"moment": "^2.17.1",
"nodemon": "1.10.0",
"opn-cli": "3.1.0"
},
Expand Down
3 changes: 2 additions & 1 deletion spec/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"jasmine": false,
"spyOn": false,
"xdescribe": false,
"xit": false
"xit": false,
"fail": false
}
}
Loading