diff --git a/src/modules/date/index.ts b/src/modules/date/index.ts index 564e7b4d745..b076d4f6c94 100644 --- a/src/modules/date/index.ts +++ b/src/modules/date/index.ts @@ -5,6 +5,15 @@ import { toDate } from '../../internal/date'; import { assertLocaleData } from '../../internal/locale-proxy'; import { SimpleModuleBase } from '../../internal/module-base'; +/** + * Small helper function to convert a number of years to an amount of milliseconds. + * + * @param years The number of years to convert to milliseconds. + */ +function yearsToMs(years: number): number { + return years * 365 * 24 * 3600 * 1000; +} + /** * Module to generate dates (without methods requiring localized data). */ @@ -47,14 +56,18 @@ export class SimpleDateModule extends SimpleModuleBase { * Generates a random date in the past. * * @param options The optional options object. - * @param options.years The range of years the date may be in the past. Defaults to `1`. + * @param options.years The range of years the date may be in the past. Either as a fixed amount of years or as a year range. Defaults to `1`. * @param options.refDate The date to use as reference point for the newly generated date. Defaults to `faker.defaultRefDate()`. * + * @throws {FakerError} If `years.max` is less than 0. + * @throws {FakerError} If `years.min` is greater or equal than `years.max`. + * * @see faker.date.recent(): For generating dates in the recent past (days instead of years). * * @example * faker.date.past() // '2021-12-03T05:40:44.408Z' * faker.date.past({ years: 10 }) // '2017-10-25T21:34:19.488Z' + * faker.date.past({ years: { min: 4, max: 7 } }) // '2022-12-12T03:43:16.434Z' * faker.date.past({ years: 10, refDate: '2020-01-01T00:00:00.000Z' }) // '2017-08-18T02:59:12.350Z' * * @since 8.0.0 @@ -66,7 +79,22 @@ export class SimpleDateModule extends SimpleModuleBase { * * @default 1 */ - years?: number; + years?: + | number + | { + /** + * The minimum amount of years the date should be in the past. + * + * @default 0 + */ + min: number; + /** + * The maximum amount of years the date should be in the past. + * + * @default 1 + */ + max: number; + }; /** * The date to use as reference point for the newly generated date. * @@ -75,17 +103,27 @@ export class SimpleDateModule extends SimpleModuleBase { refDate?: string | Date | number; } = {} ): Date { - const { years = 1, refDate = this.faker.defaultRefDate() } = options; + const { refDate = this.faker.defaultRefDate() } = options; + let { years = 1 } = options; + if (typeof years === 'number') { + years = { min: 0, max: years }; + } - if (years <= 0) { + if (years.max <= 0) { throw new FakerError('Years must be greater than 0.'); } + if (years.min >= years.max) { + throw new FakerError( + 'The maximum amount of years must be greater than the minimum amount of years.' + ); + } + const time = toDate(refDate).getTime(); return this.between({ - from: time - years * 365 * 24 * 3600 * 1000, - to: time - 1000, + from: time - yearsToMs(years.max), + to: time - yearsToMs(years.min) - 1000, }); } @@ -93,14 +131,18 @@ export class SimpleDateModule extends SimpleModuleBase { * Generates a random date in the future. * * @param options The optional options object. - * @param options.years The range of years the date may be in the future. Defaults to `1`. + * @param options.years The range of years the date may be in the future. Either as a fixed amount of years or as a year range. Defaults to `1`. * @param options.refDate The date to use as reference point for the newly generated date. Defaults to `faker.defaultRefDate()`. * + * @throws {FakerError} If `years.max` is less than 0. + * @throws {FakerError} If `years.min` is greater or equal than `years.max`. + * * @see faker.date.soon(): For generating dates in the near future (days instead of years). * * @example * faker.date.future() // '2022-11-19T05:52:49.100Z' * faker.date.future({ years: 10 }) // '2030-11-23T09:38:28.710Z' + * faker.date.future({ years: { min: 4, max: 7 } }) // '2031-05-21T05:49:21.116Z' * faker.date.future({ years: 10, refDate: '2020-01-01T00:00:00.000Z' }) // '2020-12-13T22:45:10.252Z' * * @since 8.0.0 @@ -112,7 +154,22 @@ export class SimpleDateModule extends SimpleModuleBase { * * @default 1 */ - years?: number; + years?: + | number + | { + /** + * The minimum amount of years the date should be in the future. + * + * @default 0 + */ + min: number; + /** + * The maximum amount of years the date should be in the future. + * + * @default 1 + */ + max: number; + }; /** * The date to use as reference point for the newly generated date. * @@ -121,17 +178,27 @@ export class SimpleDateModule extends SimpleModuleBase { refDate?: string | Date | number; } = {} ): Date { - const { years = 1, refDate = this.faker.defaultRefDate() } = options; + const { refDate = this.faker.defaultRefDate() } = options; + let { years = 1 } = options; + if (typeof years === 'number') { + years = { min: 0, max: years }; + } - if (years <= 0) { + if (years.max <= 0) { throw new FakerError('Years must be greater than 0.'); } + if (years.min >= years.max) { + throw new FakerError( + 'The maximum amount of years must be greater than the minimum amount of years.' + ); + } + const time = toDate(refDate).getTime(); return this.between({ - from: time + 1000, - to: time + years * 365 * 24 * 3600 * 1000, + from: time + yearsToMs(years.min) + 1000, + to: time + yearsToMs(years.max), }); } diff --git a/test/modules/__snapshots__/date.spec.ts.snap b/test/modules/__snapshots__/date.spec.ts.snap index ee5faa6d7fc..1db296083e3 100644 --- a/test/modules/__snapshots__/date.spec.ts.snap +++ b/test/modules/__snapshots__/date.spec.ts.snap @@ -81,7 +81,9 @@ exports[`date > 42 > future > with only number refDate 1`] = `2021-07-08T10:07:3 exports[`date > 42 > future > with only string refDate 1`] = `2021-07-08T10:07:33.524Z`; -exports[`date > 42 > future > with value 1`] = `2024-11-19T18:52:08.216Z`; +exports[`date > 42 > future > with value numeric 1`] = `2024-11-19T18:52:08.216Z`; + +exports[`date > 42 > future > with value range 1`] = `2027-07-06T01:53:51.028Z`; exports[`date > 42 > month > noArgs 1`] = `"January"`; @@ -97,7 +99,9 @@ exports[`date > 42 > past > with only number refDate 1`] = `2020-07-08T10:07:32. exports[`date > 42 > past > with only string refDate 1`] = `2020-07-08T10:07:32.524Z`; -exports[`date > 42 > past > with value 1`] = `2014-11-22T18:52:07.216Z`; +exports[`date > 42 > past > with value numeric 1`] = `2014-11-22T18:52:07.216Z`; + +exports[`date > 42 > past > with value range 1`] = `2012-07-09T01:53:50.028Z`; exports[`date > 42 > recent > with only Date refDate 1`] = `2021-02-21T02:08:35.603Z`; @@ -207,7 +211,9 @@ exports[`date > 1211 > future > with only number refDate 1`] = `2022-01-26T14:59 exports[`date > 1211 > future > with only string refDate 1`] = `2022-01-26T14:59:27.356Z`; -exports[`date > 1211 > future > with value 1`] = `2030-06-03T19:31:11.518Z`; +exports[`date > 1211 > future > with value numeric 1`] = `2030-06-03T19:31:11.518Z`; + +exports[`date > 1211 > future > with value range 1`] = `2032-06-28T21:40:59.944Z`; exports[`date > 1211 > month > noArgs 1`] = `"September"`; @@ -223,7 +229,9 @@ exports[`date > 1211 > past > with only number refDate 1`] = `2021-01-26T14:59:2 exports[`date > 1211 > past > with only string refDate 1`] = `2021-01-26T14:59:26.356Z`; -exports[`date > 1211 > past > with value 1`] = `2020-06-05T19:31:10.518Z`; +exports[`date > 1211 > past > with value numeric 1`] = `2020-06-05T19:31:10.518Z`; + +exports[`date > 1211 > past > with value range 1`] = `2017-07-02T21:40:58.944Z`; exports[`date > 1211 > recent > with only Date refDate 1`] = `2021-02-21T15:26:18.924Z`; @@ -331,7 +339,9 @@ exports[`date > 1337 > future > with only number refDate 1`] = `2021-05-28T08:29 exports[`date > 1337 > future > with only string refDate 1`] = `2021-05-28T08:29:26.600Z`; -exports[`date > 1337 > future > with value 1`] = `2023-10-06T02:30:57.962Z`; +exports[`date > 1337 > future > with value numeric 1`] = `2023-10-06T02:30:57.962Z`; + +exports[`date > 1337 > future > with value range 1`] = `2026-07-01T11:10:47.810Z`; exports[`date > 1337 > month > noArgs 1`] = `"February"`; @@ -347,7 +357,9 @@ exports[`date > 1337 > past > with only number refDate 1`] = `2020-05-28T08:29:2 exports[`date > 1337 > past > with only string refDate 1`] = `2020-05-28T08:29:25.600Z`; -exports[`date > 1337 > past > with value 1`] = `2013-10-08T02:30:56.962Z`; +exports[`date > 1337 > past > with value numeric 1`] = `2013-10-08T02:30:56.962Z`; + +exports[`date > 1337 > past > with value range 1`] = `2011-07-05T11:10:46.810Z`; exports[`date > 1337 > recent > with only Date refDate 1`] = `2021-02-20T23:26:34.381Z`; diff --git a/test/modules/date.spec.ts b/test/modules/date.spec.ts index f93135ba1c7..5e07dfe4e47 100644 --- a/test/modules/date.spec.ts +++ b/test/modules/date.spec.ts @@ -44,7 +44,8 @@ describe('date', () => { .it('with only number refDate', { refDate: new Date(refDate).getTime(), }) - .it('with value', { years: 10, refDate }); + .it('with value numeric', { years: 10, refDate }) + .it('with value range', { years: { min: 3, max: 12 }, refDate }); }); t.describeEach( @@ -190,6 +191,25 @@ describe('date', () => { expect(date).greaterThanOrEqual(yearsAgo); }); + it('should return a date between 20 and 40 years in the past', () => { + const today = new Date(); + const yearsAgoMax = 40; + const yearAgoMax = new Date(today); + yearAgoMax.setFullYear(yearAgoMax.getFullYear() - yearsAgoMax); + + const yearsAgoMin = 20; + const yearAgoMin = new Date(today); + yearAgoMin.setFullYear(yearAgoMin.getFullYear() - yearsAgoMin); + + const date = faker.date.past({ + years: { min: yearsAgoMin, max: yearsAgoMax }, + }); + + expect(date).lessThan(today); + expect(date).lessThan(yearAgoMin); + expect(date).greaterThanOrEqual(yearAgoMax); + }); + it('should throw an error when years = 0', () => { const refDate = new Date(); expect(() => @@ -197,6 +217,34 @@ describe('date', () => { ).toThrow(new FakerError('Years must be greater than 0.')); }); + it('should throw an error when years.min > years.max', () => { + const refDate = new Date(); + expect(() => + faker.date.past({ + years: { min: 3, max: 2 }, + refDate: refDate.toISOString(), + }) + ).toThrow( + new FakerError( + 'The maximum amount of years must be greater than the minimum amount of years.' + ) + ); + }); + + it('should throw an error when years.min = years.max', () => { + const refDate = new Date(); + expect(() => + faker.date.past({ + years: { min: 6, max: 6 }, + refDate: refDate.toISOString(), + }) + ).toThrow( + new FakerError( + 'The maximum amount of years must be greater than the minimum amount of years.' + ) + ); + }); + it.each(converterMap)( 'should return a past date relative to given refDate', (converter) => { @@ -216,9 +264,34 @@ describe('date', () => { describe('future()', () => { it('should return a date 75 years into the future', () => { - const date = faker.date.future({ years: 75 }); + const today = new Date(); + const yearsUntilMax = 75; + const yearUntilMax = new Date(today); + yearUntilMax.setFullYear(yearUntilMax.getFullYear() + yearsUntilMax); - expect(date).greaterThan(new Date()); + const date = faker.date.future({ years: yearsUntilMax }); + + expect(date).greaterThan(today); + expect(date).lessThanOrEqual(yearUntilMax); + }); + + it('should return a date between 20 and 40 years in the future', () => { + const today = new Date(); + const yearsUntilMin = 20; + const yearUntilMin = new Date(today); + yearUntilMin.setFullYear(yearUntilMin.getFullYear() + yearsUntilMin); + + const yearsUntilMax = 40; + const yearUntilMax = new Date(today); + yearUntilMax.setFullYear(yearUntilMax.getFullYear() + yearsUntilMax); + + const date = faker.date.future({ + years: { min: yearsUntilMin, max: yearsUntilMax }, + }); + + expect(date).greaterThan(today); + expect(date).greaterThan(yearUntilMin); + expect(date).lessThanOrEqual(yearUntilMax); }); it('should throw an error when years = 0', () => { @@ -228,6 +301,34 @@ describe('date', () => { ).toThrow(new FakerError('Years must be greater than 0.')); }); + it('should throw an error when years.min > years.max', () => { + const refDate = new Date(); + expect(() => + faker.date.future({ + years: { min: 3, max: 2 }, + refDate: refDate.toISOString(), + }) + ).toThrow( + new FakerError( + 'The maximum amount of years must be greater than the minimum amount of years.' + ) + ); + }); + + it('should throw an error when years.min = years.max', () => { + const refDate = new Date(); + expect(() => + faker.date.future({ + years: { min: 6, max: 6 }, + refDate: refDate.toISOString(), + }) + ).toThrow( + new FakerError( + 'The maximum amount of years must be greater than the minimum amount of years.' + ) + ); + }); + it.each(converterMap)( 'should return a date 75 years after the date given', (converter) => {