'use strict'; // Load modules const Any = require('./any'); const Ref = require('./ref'); const Hoek = require('hoek'); const Moment = require('moment'); // Declare internals const internals = {}; internals.isoDate = /^(?:\d{4}(?!\d{2}\b))(?:(-?)(?:(?:0[1-9]|1[0-2])(?:\1(?:[12]\d|0[1-9]|3[01]))?|W(?:[0-4]\d|5[0-2])(?:-?[1-7])?|(?:00[1-9]|0[1-9]\d|[12]\d{2}|3(?:[0-5]\d|6[1-6])))(?![T]$|[T][\d]+Z$)(?:[T\s](?:(?:(?:[01]\d|2[0-3])(?:(:?)[0-5]\d)?|24\:?00)(?:[.,]\d+(?!:))?)(?:\2[0-5]\d(?:[.,]\d+)?)?(?:[Z]|(?:[+-])(?:[01]\d|2[0-3])(?::?[0-5]\d)?)?)?)?$/; internals.invalidDate = new Date(''); internals.isIsoDate = (() => { const isoString = internals.isoDate.toString(); return (date) => { return date && (date.toString() === isoString); }; })(); internals.Date = class extends Any { constructor() { super(); this._type = 'date'; } _base(value, state, options) { const result = { value: (options.convert && internals.Date.toDate(value, this._flags.format, this._flags.timestamp, this._flags.multiplier)) || value }; if (result.value instanceof Date && !isNaN(result.value.getTime())) { result.errors = null; } else if (!options.convert) { result.errors = this.createError('date.strict', null, state, options); } else { let context = null; let type; if (internals.isIsoDate(this._flags.format)) { type = 'isoDate'; } else if (this._flags.timestamp) { type = 'timestamp.' + this._flags.timestamp; } else if (this._flags.format) { type = 'format'; context = { format: this._flags.format }; } else { type = 'base'; } result.errors = this.createError('date.' + type, context, state, options); } return result; } static toDate(value, format, timestamp, multiplier) { if (value instanceof Date) { return value; } if (typeof value === 'string' || (typeof value === 'number' && !isNaN(value) && isFinite(value))) { if (typeof value === 'string' && /^[+-]?\d+(\.\d+)?$/.test(value)) { value = parseFloat(value); } let date; if (format) { if (internals.isIsoDate(format)) { date = format.test(value) ? new Date(value) : internals.invalidDate; } else { date = Moment(value, format, true); date = date.isValid() ? date.toDate() : internals.invalidDate; } } else if (timestamp && multiplier) { date = new Date(value * multiplier); } else { date = new Date(value); } if (!isNaN(date.getTime())) { return date; } } return null; } format(format) { Hoek.assert(typeof format === 'string' || (Array.isArray(format) && format.every((f) => typeof f === 'string')), 'Invalid format.'); const obj = this.clone(); obj._flags.format = format; return obj; } iso() { const obj = this.clone(); obj._flags.format = internals.isoDate; return obj; } timestamp(type) { type = type || 'javascript'; const allowed = ['javascript', 'unix']; Hoek.assert(allowed.indexOf(type) !== -1, '"type" must be one of "' + allowed.join('", "') + '"'); const obj = this.clone(); obj._flags.timestamp = type; obj._flags.multiplier = type === 'unix' ? 1000 : 1; return obj; } _isIsoDate(value) { return internals.isoDate.test(value); } }; internals.compare = function (type, compare) { return function (date) { const isNow = date === 'now'; const isRef = Ref.isRef(date); if (!isNow && !isRef) { date = internals.Date.toDate(date); } Hoek.assert(date, 'Invalid date format'); return this._test(type, date, function (value, state, options) { let compareTo; if (isNow) { compareTo = Date.now(); } else if (isRef) { compareTo = internals.Date.toDate(date(state.parent, options)); if (!compareTo) { return this.createError('date.ref', { ref: date.key }, state, options); } compareTo = compareTo.getTime(); } else { compareTo = date.getTime(); } if (compare(value.getTime(), compareTo)) { return value; } return this.createError('date.' + type, { limit: new Date(compareTo) }, state, options); }); }; }; internals.Date.prototype.min = internals.compare('min', (value, date) => value >= date); internals.Date.prototype.max = internals.compare('max', (value, date) => value <= date); module.exports = new internals.Date();