@dwp/govuk-casa
Version:
Framework for creating basic GOVUK Collect-And-Submit-Applications
138 lines (127 loc) • 4.68 kB
JavaScript
const moment = require('moment');
const ValidationError = require('../ValidationError.js');
/**
* Date object format:
* {
* dd: <string>,
* mm: <string>,
* yyyy: <string>
* }.
*
* Note that the time part of any injected "moment" objects will be zero'ed, as
* we are only interested in the date component (minimum day resolution).
*
* Bound attributes:
* string|object errorMsg = Error message to use on validation failure
* moment.duration afterOffsetFromNow = Date must be after offset from now
* string|object errorMsgAfterOffset = Error for afterOffsetFromNow failure
* moment.duration beforeOffsetFromNow = Date must be before offset from now
* string|object errorMsgBeforeOffset = Error for beforeOffsetFromNow failure
* bool allowMonthNames = Allow "Jan", "January", etc (default = false)
* bool allowSingleDigitDay = Allow "1" rather than "01" (default = false)
* bool allowSingleDigitMonth = Allow "1" rather than "01" (default = false)
* moment now = Override the notion of "now" (useful for testing)
*
* @param {object} value Date object (see description)
* @param {object} dataContext Context
* @returns {Promise} Promise
*/
function dateObject(value, dataContext = {}) {
const config = {
errorMsg: {
inline: 'validation:rule.dateObject.inline',
summary: 'validation:rule.dateObject.summary',
},
errorMsgAfterOffset: {
inline: 'validation:rule.dateObject.afterOffset.inline',
summary: 'validation:rule.dateObject.afterOffset.summary',
},
errorMsgBeforeOffset: {
inline: 'validation:rule.dateObject.beforeOffset.inline',
summary: 'validation:rule.dateObject.beforeOffset.summary',
},
now: moment.utc(),
allowSingleDigitDay: false,
allowSingleDigitMonth: false,
allowMonthNames: false,
afterOffsetFromNow: undefined,
beforeOffsetFromNow: undefined,
...this,
};
let valid = false;
let { errorMsg } = config;
let momentDate;
let NOW = config.now;
NOW = moment([NOW.year(), NOW.month(), NOW.date(), 0, 0, 0]);
// Accepted formats
let formats = ['DD-MM-YYYY'];
const formatTests = [{
flags: [config.allowSingleDigitDay],
formats: ['D-MM-YYYY'],
}, {
flags: [config.allowSingleDigitDay, config.allowSingleDigitMonth],
formats: ['D-M-YYYY'],
}, {
flags: [config.allowSingleDigitDay, config.allowMonthNames],
formats: ['D-MMM-YYYY', 'D-MMMM-YYYY'],
}, {
flags: [config.allowSingleDigitMonth],
formats: ['DD-M-YYYY'],
}, {
flags: [config.allowMonthNames],
formats: ['DD-MMM-YYYY', 'DD-MMMM-YYYY'],
}];
formatTests.forEach((test) => {
if (test.flags.every((v) => v === true)) {
formats = formats.concat(test.formats);
}
});
if (typeof value === 'object') {
momentDate = moment(
[value.dd, value.mm, value.yyyy].join('-'),
formats,
true,
).startOf('date');
valid = momentDate.isValid();
// Check date is after the specified duration from now.
// Need to use UTC() otherwise DST shifts can affect the calculated offset
if (config.afterOffsetFromNow) {
const offsetDate = moment(NOW).add(config.afterOffsetFromNow).startOf('date');
if (momentDate.isSameOrBefore(offsetDate)) {
valid = false;
errorMsg = config.errorMsgAfterOffset;
}
}
// Check date is before the specified duration from now
// Need to use UTC() otherwise DST shifts can affect the calculated offset
if (config.beforeOffsetFromNow) {
const offsetDate = moment(NOW).add(config.beforeOffsetFromNow).startOf('date');
if (momentDate.isSameOrAfter(offsetDate)) {
valid = false;
errorMsg = config.errorMsgBeforeOffset;
}
}
// Check presence of each object component (dd, mm, yyyy) in order to log
// which specific parts are in error
errorMsg.focusSuffix = [];
if (!Object.prototype.hasOwnProperty.call(value, 'dd') || !value.dd) {
errorMsg.focusSuffix.push('[dd]');
}
if (!Object.prototype.hasOwnProperty.call(value, 'mm') || !value.mm) {
errorMsg.focusSuffix.push('[mm]');
}
if (!Object.prototype.hasOwnProperty.call(value, 'yyyy') || !value.yyyy) {
errorMsg.focusSuffix.push('[yyyy]');
}
// If the date is invalid, but not specific parts have been highighted in
// error, then highlight all inputs, focusing on the [dd] first
if (!valid && !errorMsg.focusSuffix.length) {
errorMsg.focusSuffix = ['[dd]', '[mm]', '[yyyy]'];
}
}
return valid ? Promise.resolve() : Promise.reject(ValidationError.make({
errorMsg,
dataContext,
}));
}
module.exports = dateObject;