Skip to content

Commit

Permalink
feat: add support for parsing iso8601 dates
Browse files Browse the repository at this point in the history
Fixes: #65
  • Loading branch information
thetutlage committed Nov 29, 2024
1 parent b782d4c commit fe61951
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 8 deletions.
32 changes: 24 additions & 8 deletions src/schema/date/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,33 +37,49 @@ export const dateRule = createRule<Partial<DateFieldOptions>>((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
*/
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
Expand Down
31 changes: 31 additions & 0 deletions tests/integration/schema/date.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})

0 comments on commit fe61951

Please sign in to comment.