keystone
Version:
Web Application Framework and Admin GUI / Content Management System built on Express.js and Mongoose
211 lines (190 loc) • 6.79 kB
JavaScript
var FieldType = require('../Type');
var moment = require('moment');
var util = require('util');
var utils = require('keystone-utils');
var TextType = require('../text/TextType');
/**
* Date FieldType Constructor
* @extends Field
* @api public
*/
function date (list, path, options) {
this._nativeType = Date;
this._underscoreMethods = ['format', 'moment', 'parse'];
this._fixedSize = 'medium';
this._properties = ['formatString', 'yearRange', 'isUTC', 'inputFormat', 'todayButton'];
this.parseFormatString = options.inputFormat || 'YYYY-MM-DD';
this.formatString = (options.format === false) ? false : (options.format || 'Do MMM YYYY');
this.yearRange = options.yearRange;
this.isUTC = options.utc || false;
this.todayButton = typeof options.todayButton !== 'undefined' ? options.todayButton : true;
/*
* 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.');
}
date.super_.call(this, list, path, options);
}
date.properName = 'Date';
util.inherits(date, FieldType);
date.prototype.validateRequiredInput = TextType.prototype.validateRequiredInput;
/**
* Add filters to a query
*/
date.prototype.addFilterToQuery = function (filter) {
var query = {};
if (filter.mode === 'between') {
if (filter.after && filter.before) {
filter.after = moment(filter.after);
filter.before = moment(filter.before);
if (filter.after.isValid() && filter.before.isValid()) {
query[this.path] = {
$gte: filter.after.startOf('day').toDate(),
$lte: filter.before.endOf('day').toDate(),
};
}
}
} else if (filter.value) {
var day = {
moment: moment(filter.value),
};
day.start = day.moment.startOf('day').toDate();
day.end = moment(filter.value).endOf('day').toDate();
if (day.moment.isValid()) {
if (filter.mode === 'after') {
query[this.path] = { $gt: day.end };
} else if (filter.mode === 'before') {
query[this.path] = { $lt: day.start };
} else {
query[this.path] = { $gte: day.start, $lte: day.end };
}
}
}
if (filter.inverted) {
query[this.path] = { $not: query[this.path] };
}
return query;
};
/**
* Formats the field value
*/
date.prototype.format = function (item, format) {
if (format || this.formatString) {
return item.get(this.path) ? this.moment(item).format(format || this.formatString) : '';
} else {
return item.get(this.path) || '';
}
};
/**
* Returns a new `moment` object with the field value
*/
date.prototype.moment = function (item) {
var m = moment(item.get(this.path));
if (this.isUTC) m.utc();
return m;
};
/**
* Parses input with the correct moment version (normal or utc) and uses
* either the provided input format or the default for the field
*/
date.prototype.parse = function (value, format, strict) {
var m = this.isUTC ? moment.utc : moment;
// TODO Check should maybe be if (typeof value === 'string')
// use the parseFormatString. Ever relevant?
if (typeof value === 'number' || value instanceof Date) {
return m(value);
} else {
return m(value, format || this.parseFormatString, strict);
}
};
/**
* Asynchronously confirms that the provided date is valid
*/
date.prototype.validateInput = function (data, callback) {
var value = this.getValueFromData(data);
var result = true;
if (value) {
result = this.parse(value).isValid();
}
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
*
* Deprecated
*/
date.prototype.inputIsValid = function (data, required, item) {
if (!(this.path in data) && item && item.get(this.path)) return true;
var newValue = moment(data[this.path], this.parseFormatString);
if (required && (!newValue.isValid())) {
return false;
} else if (data[this.path] && newValue && !newValue.isValid()) {
return false;
} else {
return true;
}
};
/**
* Updates the value for this field in the item from a data object
*/
date.prototype.updateItem = function (item, data, callback) {
var value = this.getValueFromData(data);
if (value !== null && value !== '') {
// If the value is not null, empty string or undefined, parse it
var newValue = this.parse(value);
// If it's valid and not the same as the last value, save it
if (newValue.isValid() && (!item.get(this.path) || !newValue.isSame(item.get(this.path)))) {
item.set(this.path, newValue.toDate());
}
} else {
// If it's null or empty string, clear it out
item.set(this.path, null);
}
process.nextTick(callback);
};
/* Export Field Type */
module.exports = date;