'use strict'; const Assert = require('@hapi/hoek/lib/assert'); const Any = require('./any'); const Common = require('../common'); const Template = require('../template'); const internals = {}; internals.isDate = function (value) { return value instanceof Date; }; module.exports = Any.extend({ type: 'date', coerce: { from: ['number', 'string'], method(value, { schema }) { return { value: internals.parse(value, schema._flags.format) || value }; } }, validate(value, { schema, error, prefs }) { if (value instanceof Date && !isNaN(value.getTime())) { return; } const format = schema._flags.format; if (!prefs.convert || !format || typeof value !== 'string') { return { value, errors: error('date.base') }; } return { value, errors: error('date.format', { format }) }; }, rules: { compare: { method: false, validate(value, helpers, { date }, { name, operator, args }) { const to = date === 'now' ? Date.now() : date.getTime(); if (Common.compare(value.getTime(), to, operator)) { return value; } return helpers.error('date.' + name, { limit: args.date, value }); }, args: [ { name: 'date', ref: true, normalize: (date) => { return date === 'now' ? date : internals.parse(date); }, assert: (date) => date !== null, message: 'must have a valid date format' } ] }, format: { method(format) { Assert(['iso', 'javascript', 'unix'].includes(format), 'Unknown date format', format); return this.$_setFlag('format', format); } }, greater: { method(date) { return this.$_addRule({ name: 'greater', method: 'compare', args: { date }, operator: '>' }); } }, iso: { method() { return this.format('iso'); } }, less: { method(date) { return this.$_addRule({ name: 'less', method: 'compare', args: { date }, operator: '<' }); } }, max: { method(date) { return this.$_addRule({ name: 'max', method: 'compare', args: { date }, operator: '<=' }); } }, min: { method(date) { return this.$_addRule({ name: 'min', method: 'compare', args: { date }, operator: '>=' }); } }, timestamp: { method(type = 'javascript') { Assert(['javascript', 'unix'].includes(type), '"type" must be one of "javascript, unix"'); return this.format(type); } } }, cast: { number: { from: internals.isDate, to(value, helpers) { return value.getTime(); } }, string: { from: internals.isDate, to(value, { prefs }) { return Template.date(value, prefs); } } }, messages: { 'date.base': '{{#label}} must be a valid date', 'date.format': '{{#label}} must be in {msg("date.format." + #format) || #format} format', 'date.greater': '{{#label}} must be greater than {{:#limit}}', 'date.less': '{{#label}} must be less than {{:#limit}}', 'date.max': '{{#label}} must be less than or equal to {{:#limit}}', 'date.min': '{{#label}} must be greater than or equal to {{:#limit}}', // Messages used in date.format 'date.format.iso': 'ISO 8601 date', 'date.format.javascript': 'timestamp or number of milliseconds', 'date.format.unix': 'timestamp or number of seconds' } }); // Helpers internals.parse = function (value, format) { if (value instanceof Date) { return value; } if (typeof value !== 'string' && (isNaN(value) || !isFinite(value))) { return null; } if (/^\s*$/.test(value)) { return null; } // ISO if (format === 'iso') { if (!Common.isIsoDate(value)) { return null; } return internals.date(value.toString()); } // Normalize number string const original = value; if (typeof value === 'string' && /^[+-]?\d+(\.\d+)?$/.test(value)) { value = parseFloat(value); } // Timestamp if (format) { if (format === 'javascript') { return internals.date(1 * value); // Casting to number } if (format === 'unix') { return internals.date(1000 * value); } if (typeof original === 'string') { return null; } } // Plain return internals.date(value); }; internals.date = function (value) { const date = new Date(value); if (!isNaN(date.getTime())) { return date; } return null; };