diff --git a/src/schema/date/rules.ts b/src/schema/date/rules.ts index 94e2e16..0498a75 100644 --- a/src/schema/date/rules.ts +++ b/src/schema/date/rules.ts @@ -37,10 +37,11 @@ export const dateRule = createRule>((value, options, f } let isTimestampAllowed = false + let isISOAllowed = false let formats: DateEqualsOptions['format'] = options.formats || DEFAULT_DATE_FORMATS /** - * DayJS mutates the formats property under the hood. There + * DayJS mutates the formats property under the hood. Therefore * we have to create a shallow clone before passing formats. * * https://github.com/iamkun/dayjs/issues/2136 @@ -48,22 +49,37 @@ export const dateRule = createRule>((value, options, f if (Array.isArray(formats)) { formats = [...formats] isTimestampAllowed = formats.includes('x') + isISOAllowed = formats.includes('iso8601') } else if (typeof formats !== 'string') { formats = { ...formats } isTimestampAllowed = formats.format === 'x' + isISOAllowed = formats.format === 'iso' } const valueAsNumber = isTimestampAllowed ? helpers.asNumber(value) : value + let dateTime: dayjs.Dayjs | undefined + /** - * The timestamp validation does not work with formats array - * when using "customFormatsPlugin". Therefore we have - * to create dayjs instance without formats option + * The timestamp validation does not work with formats array. + * Therefore we validate is separately without passing any + * formats. + * + * Otherwise we parse the date with formats */ - const dateTime = - isTimestampAllowed && !Number.isNaN(valueAsNumber) - ? dayjs(valueAsNumber) - : dayjs(value, formats, true) + if (isTimestampAllowed && !Number.isNaN(valueAsNumber)) { + dateTime = dayjs(valueAsNumber) + } else { + dateTime = dayjs(value, formats, true) + } + + /** + * If datetime is invalid and the ISO format is allowed, + * then we reattempt to parse the date without formats + */ + if (!dateTime.isValid() && isISOAllowed) { + dateTime = dayjs(value) + } /** * Ensure post parsing the datetime instance is valid diff --git a/tests/integration/schema/date.spec.ts b/tests/integration/schema/date.spec.ts index 713d4a9..c5c66c1 100644 --- a/tests/integration/schema/date.spec.ts +++ b/tests/integration/schema/date.spec.ts @@ -168,4 +168,35 @@ test.group('VineDate', () => { 'Invalid datetime value "foo" provided to the afterOrEqual rule' ) }) + + test('allow ISO 8601 date', async ({ assert }) => { + const schema = vine.object({ + created_at: vine.date({ formats: ['iso8601'] }), + }) + + const data = { created_at: '2018-04-04T16:00:00.000Z' } + const result = await vine.validate({ schema, data }) + assert.instanceOf(result.created_at, Date) + assert.equal(result.created_at.getDate(), '4') + assert.equal(result.created_at.getMonth(), '3') + assert.equal(result.created_at.getFullYear(), '2018') + assert.equal(result.created_at.getSeconds(), '00') + assert.equal(result.created_at.getUTCHours(), '16') + assert.equal(result.created_at.getUTCMinutes(), '00') + assert.equal(result.created_at.toISOString(), '2018-04-04T16:00:00.000Z') + }) + + test('allow other formats alongside iso', async ({ assert }) => { + const schema = vine.object({ + created_at: vine.date({ formats: ['iso8601', 'YYYY/MM/DD'] }), + }) + + const data = { created_at: '2018/04/04' } + const result = await vine.validate({ schema, data }) + + assert.instanceOf(result.created_at, Date) + assert.equal(result.created_at.getDate(), '4') + assert.equal(result.created_at.getMonth(), '3') + assert.equal(result.created_at.getFullYear(), '2018') + }) })