Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
maxbeier committed Mar 24, 2017
2 parents 87b98e2 + 38d769d commit 0497282
Show file tree
Hide file tree
Showing 15 changed files with 333 additions and 210 deletions.
26 changes: 23 additions & 3 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,29 @@
New features can be requested and voted upon on:
* https://productpains.com/product/keystonejs
-->

### Expected behavior
<!-- If you're describing a bug, tell us what should happen -->
<!-- If you're suggesting a change/improvement, tell us how it should work -->

### Steps to reproduce the behavior

### Expected behavior

### Actual behavior
### Actual/Current behavior
<!-- If you're describing a bug, tell us what happens instead of the expected behavior -->
<!-- If you're suggesting a change/improvement, explain the difference from current behavior -->



### Steps to reproduce the actual/current behavior
<!-- If you're describing a bug, tell us what steps to take to reproduce your bug -->
<!-- If you'resuggesting a change/improvement, explain how to reproduce the current behavior -->



### Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->

| Software | Version
| ---------------- | -------
| Keystone |
| Node |
8 changes: 8 additions & 0 deletions docs/guides/v0.3-to-v4.0-Upgrade-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,14 @@ The `csv expanded` keystone option has been deprecated. Relationship fields will

## Other gotchas

### Breaking Fixes

#### Relationship Field Updates

Relationship fields had a bug in 0.3.x and 4.0.0 betas pre-beta.6 where `undefined` values passed to `List.updateItem` would be persisted to the database for `{ many: true }` fields.

This has been fixed, so that incoming `undefined` values are ignored, which is consistent with all other field types except `Boolean`.

### Changed Dependencies

#### Jade
Expand Down
23 changes: 13 additions & 10 deletions fields/types/date/DateField.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,22 @@ module.exports = Field.create({
value: value,
});
},
moment (value) {
var m = moment(value);
if (this.props.isUTC) m.utc();
return m;
toMoment (value) {
if (this.props.isUTC) {
return moment.utc(value);
} else {
return moment(value);
}
},
isValid (value) {
return this.moment(value, this.inputFormat).isValid();
return this.toMoment(value, this.inputFormat).isValid();
},
format (value) {
return value ? this.moment(value).format(this.props.formatString) : '';
return value ? this.toMoment(value).format(this.props.formatString) : '';
},
setToday () {
this.valueChanged({
value: this.moment(new Date()).format(this.props.inputFormat),
value: this.toMoment(new Date()).format(this.props.inputFormat),
});
},
renderValue () {
Expand All @@ -67,10 +69,11 @@ module.exports = Field.create({
);
},
renderField () {
let value = this.moment(this.props.value);
value = this.props.value && value.isValid()
? value.format(this.props.inputFormat)
var dateAsMoment = this.toMoment(this.props.value);
var value = this.props.value && dateAsMoment.isValid()
? dateAsMoment.format(this.props.inputFormat)
: this.props.value;

return (
<Group>
<Section grow>
Expand Down
53 changes: 53 additions & 0 deletions fields/types/date/DateType.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ function date (list, path, options) {

this.yearRange = options.yearRange;
this.isUTC = options.utc || false;

/*
* This offset is used to determine whether or not a stored date is probably corrupted or not.
* If the date/time stored plus this offset equals a time close to midnight for that day, that
* resulting date/time will be provided via the getData method instead of the one that is stored.
* By default this timezone offset matches the offset of the keystone server. Using the default
* setting is highly recommended.
*/
this.timezoneUtcOffsetMinutes = options.timezoneUtcOffsetMinutes || moment().utcOffset();

if (this.formatString && typeof this.formatString !== 'string') {
throw new Error('FieldType.Date: options.format must be a string.');
}
Expand Down Expand Up @@ -115,6 +125,49 @@ date.prototype.validateInput = function (data, callback) {
utils.defer(callback, result);
};

/**
*
* Retrives the date as a 'Javascript Date'.
*
* Note: If the JS date retrieved is UTC and has a time other than midnight,
* it has likely become corrupted. In this instance, the below code will
* attempt to add the server offset to it to fix the date.
*/
date.prototype.getData = function (item) {
var value = item.get(this.path);
var momentDate = this.isUTC ? moment.utc(value) : moment(value);

if (this.isUTC) {
if (momentDate.format('HH:mm:ss:SSS') !== '00:00:00:000') {
// Time is NOT midnight. So, let's try and add the server timezone offset
// to convert it (back?) to the original intended time. Since we don't know
// if the time was recorded during daylight savings time or not, allow +/-
// 1 hour leeway.

var adjustedMomentDate = moment.utc(momentDate);

// Add the server the time so that it is within +/- 1 hour of midnight.
adjustedMomentDate.add(this.timezoneUtcOffsetMinutes, 'minutes');

// Add 1 hour to the time so then we know any valid date/time would be between
// 00:00 and 02:00 on the correct day
adjustedMomentDate.add(1, 'hours'); // So
var timeAsNumber = Number(adjustedMomentDate.format('HHmmssSSS'));
if (timeAsNumber >= 0 && timeAsNumber <= 20000000) {
// Time is close enough to midnight so extract the date with a zeroed (ie. midnight) time value
return adjustedMomentDate.startOf('day').toDate();
} else {
// Seems that that adding the server time offset didn't produce a time
// that is close enough to midnight. Therefore, let's use the date/time
// as-is
return momentDate.toDate();
}
}
}

return momentDate.toDate();
};

/**
* Checks that a valid date has been provided in a data object
* An empty value clears the stored value and is considered valid
Expand Down
49 changes: 49 additions & 0 deletions fields/types/date/test/type.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ var moment = require('moment');
exports.initList = function (List) {
List.add({
date: DateType,
utcDate: { type: DateType, utc: true },
utcDateForcedTZ: { type: DateType, utc: true, timezoneUtcOffsetMinutes: 330 },
nested: {
date: DateType,
},
Expand Down Expand Up @@ -74,6 +76,53 @@ exports.testFieldType = function (List) {
});
});

describe('getData', function () {
it('Retrieval of date set in current timezone', function (done) {
var testItem = new List.model();
List.fields.date.updateItem(testItem, {
date: moment('2015-01-01', 'YYYY-MM-DD'),
}, function () {
demand(List.fields.date.getData(testItem)).eql(new Date(2015, 0, 1));
done();
});
});

it('Retrieval of UTC date', function (done) {
var testItem = new List.model();
List.fields.utcDate.updateItem(testItem, {
utcDate: moment.utc('2015-01-01', 'YYYY-MM-DD'),
}, function () {
demand(List.fields.utcDate.getData(testItem)).eql(new Date(Date.UTC(2015, 0, 1)));
done();
});
});

it('Retrieval of fixable GMT date corrupted with timezone offset', function (done) {
var testItem = new List.model();
var timeToCompareInTimezone = moment.utc('2015-01-01', 'YYYY-MM-DD');
timeToCompareInTimezone.add(-List.fields.utcDateForcedTZ.timezoneUtcOffsetMinutes, 'minutes');
List.fields.utcDateForcedTZ.updateItem(testItem, {
utcDateForcedTZ: timeToCompareInTimezone, // Creates time in whatever timezone the test is run in or utcDateForcedTZ is configured to
}, function () {
demand(List.fields.utcDateForcedTZ.getData(testItem)).eql(new Date(Date.UTC(2015, 0, 1)));
done();
});
});

it('Retrieval of non-fixable GMT date corrupted with timezone offset', function (done) {
var testItem = new List.model();
var timeToCompareInTimezone = moment.utc('2015-01-01', 'YYYY-MM-DD');
timeToCompareInTimezone.add(-540, 'minutes');
List.fields.utcDateForcedTZ.updateItem(testItem, {
utcDateForcedTZ: timeToCompareInTimezone, // Creates time in whatever timezone the test is run in or utcDateForcedTZ is configured to
}, function () {
demand(List.fields.utcDateForcedTZ.getData(testItem)).eql(timeToCompareInTimezone.toDate());
done();
});
});

});

describe('validateInput', function () {
it('should validate date strings', function (done) {
List.fields.date.validateInput({ date: '2015-01-01' }, function (result) {
Expand Down
8 changes: 4 additions & 4 deletions fields/types/location/LocationType.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,17 +505,17 @@ function calculateDistance (point1, point2) {
}

/**
* Returns the distance from a [lat, lng] point in kilometres
* Returns the distance from a [lng, lat] point in kilometres
*/
location.prototype.kmFrom = function (item, point) {
return calculateDistance(this.get(this.paths.geo), point) * RADIUS_KM;
return calculateDistance(item.get(this.paths.geo), point) * RADIUS_KM;
};

/**
* Returns the distance from a [lat, lng] point in miles
* Returns the distance from a [lng, lat] point in miles
*/
location.prototype.milesFrom = function (item, point) {
return calculateDistance(this.get(this.paths.geo), point) * RADIUS_MILES;
return calculateDistance(item.get(this.paths.geo), point) * RADIUS_MILES;
};

/* Export Field Type */
Expand Down
6 changes: 6 additions & 0 deletions fields/types/location/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,9 @@ Internal status codes mimic the Google API status codes. See [Google Maps Geocod
Use of the Google Geocoding API is subject to a query limit of 2,500 geolocation requests per day, except with an enterprise license.

The Geocoding API may only be used in conjunction with a Google map; geocoding results without displaying them on a map is prohibited. Please make sure your Keystone app complies with the Google Maps API License.

## Underscore methods

`kmFrom([lng, lat])` - Takes a 2dsphere as an array of longitude then latitude, and then returns the distance in kilometres from the location's long/lat. Uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula)

`milesFrom` - Takes a 2dsphere as an array of longitude then latitude, and then returns the distance in miles from the location's long/lat. Uses the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula)
Loading

0 comments on commit 0497282

Please sign in to comment.