diff --git a/CHANGELOG.md b/CHANGELOG.md index 3872f7e..c8b317c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ ## Changelog -### [v5.19.1](https://github.com/panates/valgen/compare/v5.19.0...v5.19.1) - +### [v5.19.2](https://github.com/panates/valgen/compare/v5.19.1...v5.19.2) - + +#### 🛠 Refactoring and Updates + +- refactor: Refactored isDateString options @Eray Hanoğlu + +### [v5.19.1](https://github.com/panates/valgen/compare/v5.19.0...v5.19.1) - 19 January 2026 #### 🚀 New Features diff --git a/package-lock.json b/package-lock.json index bc6fa25..a66a88f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "valgen", - "version": "5.19.1", + "version": "5.19.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "valgen", - "version": "5.19.1", + "version": "5.19.2", "license": "MIT", "dependencies": { "@browsery/validator": "^13.15.15", diff --git a/package.json b/package.json index 8fe1ed0..84719eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "valgen", "description": "Fast runtime type validator, converter and io (encoding/decoding) library", - "version": "5.19.1", + "version": "5.19.2", "author": "Panates", "license": "MIT", "private": true, diff --git a/src/index.ts b/src/index.ts index ebf7068..ea0f154 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import * as vg from './rules/index.js'; export * from './constants.js'; export * from './core/index.js'; +export type { DatePrecision } from './rules/type-rules/is-date.js'; export type { IsObject } from './rules/type-rules/is-object.js'; const isAlpha = vg.isAlpha(); @@ -71,7 +72,7 @@ const toDateString = ( const trim = options?.trim ?? 'seconds'; let validator = toDateStringValidators.get(trim); if (!validator) { - validator = vg.isDateString({ coerce: true, trim }); + validator = vg.isDateString({ coerce: true, precisionMax: trim }); toDateStringValidators.set(trim, validator); } return validator(input, options, ctx); diff --git a/src/rules/type-rules/is-date.ts b/src/rules/type-rules/is-date.ts index 3dadc44..ff04353 100644 --- a/src/rules/type-rules/is-date.ts +++ b/src/rules/type-rules/is-date.ts @@ -64,7 +64,7 @@ export function isDate(options?: IsDateOptions) { export interface IsDateStringOptions extends ValidationOptions { precisionMin?: DatePrecision; precisionMax?: DatePrecision; - trim?: DatePrecision; + trim?: boolean; timeZone?: boolean | number; } @@ -74,38 +74,46 @@ export interface IsDateStringOptions extends ValidationOptions { * @validator isDateString */ export function isDateString(options?: IsDateStringOptions) { - const trimIdx = PRECISION_INDEX[options?.trim || 'tz'] || 9; - const precisionMin = options?.precisionMin || options?.trim || 'minutes'; + const trim = options?.trim; const precisionMax = options?.precisionMax || 'tz'; - const precisionMaxIdx = PRECISION_INDEX[precisionMax] || 9; + const precisionMaxIdx = PRECISION_INDEX[precisionMax] || 8; + let precisionMin = options?.precisionMin || 'minutes'; const precisionMinIdx = Math.min( - trimIdx, precisionMaxIdx, PRECISION_INDEX[precisionMin] || 6, ); + precisionMin = (PRECISION_INDEX_KEYS[ + PRECISION_INDEX_VALUES.indexOf(precisionMinIdx) + ] || precisionMin) as any; return validator( 'isDateString', (input: any, context: Context, _this): Nullish => { const coerce = options?.coerce ?? context.coerce; - const parsed = coerceDateString(input, options?.trim); + const parsed = coerceDateString(input, trim ? precisionMax : undefined); if (parsed) { - if ( - parsed.precision >= precisionMinIdx && - parsed.precision <= precisionMaxIdx - ) { - return coerce ? parsed.value : input; + if (parsed.precision < precisionMinIdx) { + context.fail( + _this, + `Minimum date precision should be ${precisionMin}`, + input, + options, + ); + } + if (parsed.precision > precisionMaxIdx) { + context.fail( + _this, + `Maximum date precision should be ${precisionMax}`, + input, + options, + ); } + return coerce ? parsed.value : input; } context.fail( _this, - `Value is not valid date string` + - (options?.precisionMin || options?.precisionMax - ? ` with required precision` - : ''), + `Value "${input}" is not a valid date string`, input, - { - ...options, - }, + options, ); }, options, @@ -120,12 +128,13 @@ const DATE_PATTERN2 = function coerceDateString( input: any, - precision?: DatePrecision, + trimPrecision?: DatePrecision, ): Nullish<{ value: string; precision: number; }> { - const precisionIndex = (precision ? PRECISION_INDEX[precision] : 9) || 9; + const precisionIndex = + (trimPrecision ? PRECISION_INDEX[trimPrecision] : 9) || 9; let dateParts: (string | undefined)[] | undefined; let detectedPrecision = 0; if (input instanceof Date || typeof input === 'number') { @@ -174,9 +183,8 @@ function coerceDateString( } } if (!dateParts) return; - detectedPrecision = dateParts.findIndex(v => !v); - if (detectedPrecision === -1) - detectedPrecision = Math.min(dateParts.length, 8); + detectedPrecision = dateParts[7] ? 8 : dateParts.findIndex(v => !v); + if (detectedPrecision < 0) detectedPrecision = Math.min(dateParts.length, 8); let value = dateParts[0] || '0000'; if (precisionIndex > 1) value += '-' + (dateParts[1] || '01'); if (precisionIndex > 2) value += '-' + (dateParts[2] || '01'); @@ -187,7 +195,7 @@ function coerceDateString( if (precisionIndex > 7) value += dateParts[7] || ''; return { value, - precision: detectedPrecision || 8, + precision: Math.min(precisionIndex, detectedPrecision), }; } @@ -208,6 +216,8 @@ const PRECISION_INDEX: Record = { ms: 7, tz: 8, }; +const PRECISION_INDEX_KEYS = Object.keys(PRECISION_INDEX); +const PRECISION_INDEX_VALUES = Object.values(PRECISION_INDEX); function setPrecision(d: Date, precision?: string) { switch (precision) { diff --git a/test/type-rules/is-date-string.spec.ts b/test/type-rules/is-date-string.spec.ts index eab7d16..0d09e27 100644 --- a/test/type-rules/is-date-string.spec.ts +++ b/test/type-rules/is-date-string.spec.ts @@ -24,37 +24,23 @@ describe('isDateString', () => { expect(isDateString('2020-01-10T08:30')).toEqual('2020-01-10T08:30'); expect(isDateString('2020-01-10 08:30')).toEqual('2020-01-10 08:30'); - expect(() => isDateString('2020-01-10T08')).toThrow( - 'Value is not valid date string', - ); - expect(() => isDateString('2020-01-10')).toThrow( - 'Value is not valid date string', - ); - expect(() => isDateString(undefined as any)).toThrow( - 'Value is not valid date string', - ); - expect(() => isDateString(null as any)).toThrow( - 'Value is not valid date string', - ); - expect(() => isDateString('invalid')).toThrow( - 'Value is not valid date string', - ); + expect(() => isDateString('2020-01-10T08')).toThrow(); + expect(() => isDateString('2020-01-10')).toThrow(); + expect(() => isDateString(undefined as any)).toThrow(); + expect(() => isDateString(null as any)).toThrow(); + expect(() => isDateString('invalid')).toThrow(); }); it('should validate date string with precisionMin', () => { expect(vg.isDateString({ precisionMin: 'day' })('2020-11-01')).toEqual( '2020-11-01', ); - expect(() => vg.isDateString({ precisionMin: 'day' })('2020')).toThrow( - 'Value is not valid date string with required precision', - ); + expect(() => vg.isDateString({ precisionMin: 'day' })('2020')).toThrow(); expect(vg.isDateString({ precisionMin: 'month' })('2020-11')).toEqual( '2020-11', ); - expect(() => vg.isDateString({ precisionMin: 'month' })('2020')).toThrow( - 'Value is not valid date string with required precision', - ); + expect(() => vg.isDateString({ precisionMin: 'month' })('2020')).toThrow(); }); it('should validate date string with precisionMax', () => { @@ -63,21 +49,23 @@ describe('isDateString', () => { ); expect(() => vg.isDateString({ precisionMax: 'day' })('2020-01-01T13:30'), - ).toThrow('Value is not valid date string with required precision'); + ).toThrow(); }); it('should coerce to date with given precision', () => { expect( vg.isDateString({ coerce: true, - trim: 'min', + trim: true, + precisionMax: 'min', precisionMin: 'yr', })('2020-11-01'), ).toEqual('2020-11-01T00:00'); expect( vg.isDateString({ coerce: true, - trim: 'ms', + trim: true, + precisionMax: 'ms', precisionMin: 'yr', })(new Date('2020-11-01T00:00:00.1')), ).toEqual('2020-11-01T00:00:00.100'); @@ -86,100 +74,78 @@ describe('isDateString', () => { ).toEqual('2020-11-01T00:00:00'); }); - it('should trim to given precision', () => { + it('should trim date string to given precision', () => { expect( - vg.isDateString({ coerce: true, trim: 'yr' })( + vg.isDateString({ coerce: true, precisionMax: 'yr', trim: true })( '2020-11-01T00:00:00+03:00', ), ).toEqual('2020'); expect( - vg.isDateString({ coerce: true, trim: 'month' })( + vg.isDateString({ coerce: true, precisionMax: 'month', trim: true })( '2020-11-01T00:00:00+03:00', ), ).toEqual('2020-11'); expect( - vg.isDateString({ coerce: true, trim: 'day' })('2020-11-01'), + vg.isDateString({ coerce: true, precisionMax: 'day', trim: true })( + '2020-11-01', + ), ).toEqual('2020-11-01'); expect( - vg.isDateString({ coerce: true, trim: 'hours' })( + vg.isDateString({ coerce: true, precisionMax: 'hours', trim: true })( '2020-11-01T10:23:45+03:00', ), ).toEqual('2020-11-01T10'); expect( - vg.isDateString({ coerce: true, trim: 'minutes' })( + vg.isDateString({ coerce: true, precisionMax: 'minutes', trim: true })( '2020-11-01T10:23:45+03:00', ), ).toEqual('2020-11-01T10:23'); expect( - vg.isDateString({ coerce: true, trim: 'seconds' })( + vg.isDateString({ coerce: true, precisionMax: 'seconds', trim: true })( '2020-11-01T10:23:45+03:00', ), ).toEqual('2020-11-01T10:23:45'); expect( - vg.isDateString({ coerce: true, trim: 'ms' })( + vg.isDateString({ coerce: true, precisionMax: 'ms', trim: true })( '2020-11-01T10:23:45.123+03:00', ), ).toEqual('2020-11-01T10:23:45.123'); expect( - vg.isDateString({ coerce: true, trim: 'tz' })( + vg.isDateString({ coerce: true, precisionMax: 'tz', trim: true })( '2020-11-01T10:23:45.123+03:00', ), ).toEqual('2020-11-01T10:23:45.123' + tz); }); - it('should coerce Date to date string', () => { + it('should trim Date to given precision', () => { const d = new Date('2020-11-01T10:23:45.123'); - expect(vg.isDateString({ coerce: true, trim: 'yr' })(d)).toEqual('2020'); - expect(vg.isDateString({ coerce: true, trim: 'month' })(d)).toEqual( - '2020-11', - ); - expect(vg.isDateString({ coerce: true, trim: 'day' })(d)).toEqual( - '2020-11-01', - ); - expect(vg.isDateString({ coerce: true, trim: 'hours' })(d)).toEqual( - '2020-11-01T10', - ); - expect(vg.isDateString({ coerce: true, trim: 'minutes' })(d)).toEqual( - '2020-11-01T10:23', - ); - expect(vg.isDateString({ coerce: true, trim: 'seconds' })(d)).toEqual( - '2020-11-01T10:23:45', - ); - expect(vg.isDateString({ coerce: true, trim: 'milliseconds' })(d)).toEqual( - '2020-11-01T10:23:45.123', - ); - expect(vg.isDateString({ coerce: true, trim: 'tz' })(d)).toEqual( - '2020-11-01T10:23:45.123+03:00', - ); - }); - - it('should coerce non signed numbers to date string', () => { expect( - vg.isDateString({ - coerce: true, - precisionMin: 'yr', - })('20201101'), - ).toEqual('2020-11-01T00:00:00'); + vg.isDateString({ coerce: true, precisionMax: 'yr', trim: true })(d), + ).toEqual('2020'); expect( - vg.isDateString({ - coerce: true, - precisionMin: 'yr', - })('20201101153223.123'), - ).toEqual('2020-11-01T15:32:23.123'); + vg.isDateString({ coerce: true, precisionMax: 'month', trim: true })(d), + ).toEqual('2020-11'); expect( - vg.isDateString({ coerce: true })('20250823000600.0000+0300'), - ).toEqual('2025-08-23T00:06:00.0000+03:00'); + vg.isDateString({ coerce: true, precisionMax: 'day', trim: true })(d), + ).toEqual('2020-11-01'); expect( - vg.isDateString({ - coerce: true, - precisionMin: 'yr', - })('20201101153223.123+03'), - ).toEqual('2020-11-01T15:32:23.123+03'); + vg.isDateString({ coerce: true, precisionMax: 'hours', trim: true })(d), + ).toEqual('2020-11-01T10'); + expect( + vg.isDateString({ coerce: true, precisionMax: 'minutes', trim: true })(d), + ).toEqual('2020-11-01T10:23'); + expect( + vg.isDateString({ coerce: true, precisionMax: 'seconds', trim: true })(d), + ).toEqual('2020-11-01T10:23:45'); expect( vg.isDateString({ coerce: true, - precisionMin: 'yr', - })('20201101153223.123+0330'), - ).toEqual('2020-11-01T15:32:23.123+03:30'); + precisionMax: 'milliseconds', + trim: true, + })(d), + ).toEqual('2020-11-01T10:23:45.123'); + expect( + vg.isDateString({ coerce: true, precisionMax: 'tz', trim: true })(d), + ).toEqual('2020-11-01T10:23:45.123+03:00'); }); });