From 220911f68053ab4dd49794ac2e9cff1e0cc885e7 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sun, 15 May 2022 16:35:39 +0200 Subject: [PATCH 1/3] feat: add date.birthdate Co-authored-by: Priyansh --- src/modules/date/index.ts | 58 +++++++++++++ test/date.spec.ts | 173 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+) diff --git a/src/modules/date/index.ts b/src/modules/date/index.ts index 09f608f0490..8eca4d52017 100644 --- a/src/modules/date/index.ts +++ b/src/modules/date/index.ts @@ -253,4 +253,62 @@ export class _Date { return this.faker.helpers.arrayElement(source[type]); } + + /** + * Returns a random birthdate. + * + * @param options The options to use to generate the birthdate. If no options are set, then an age between 18 and 80 (inclusive) is generated. + * @param options.min The minimum age or year to generate a birthdate. + * @param options.max The maximum age or year to generate a birthdate. + * @param options.refDate The date to use as reference point for the newly generated date. Defaults to now. + * @param options.mode The mode to generate the birthdate. Supported modes are 'year' and 'age'. + * + * There are two modes available 'age' and 'year' (default). + * - 'age': The min and max options define the age of the person (e.g. 18 - 42) + * - 'year': The min and max options define the range the birthdate may be in. (e.g. 1900 - 2000) + * + * @example + * faker.date.birthdate(); // 2016-08-12T03:24:00.000Z + * faker.date.birthdate({min: 18, max: 65, mode: 'age'}); // 1994-02-11T03:24:00.000Z + * faker.date.birthdate({min: 1900, max: 2000, mode: 'year'}); // 1995-02-11T03:24:00.000Z + */ + birthdate(options?: { + min?: number; + max?: number; + mode?: 'age' | 'year'; + refDate?: string; + }): Date { + // Generate a random birthday date between two years, min and max + options = options || {}; + + const mode = options.mode === 'age' ? 'age' : 'year'; + let now = new Date(); + if (typeof options.refDate !== 'undefined') { + now = new Date(Date.parse(options.refDate)); + } + + let min = 0; + let max = 0; + + // If no min or max is specified, generate a random date between (now - 80) years and (now - 18) years respectively + // So that people can still be considered as adults in most cases + + if (mode === 'age') { + min = now.getFullYear() - (options.max ?? 80); + max = now.getFullYear() - (options.min ?? 18); + } else { + min = options.min ?? now.getFullYear() - 80; + max = options.max ?? now.getFullYear() - 18; + } + + const startDate = now.setFullYear(min); + const endDate = now.setFullYear(max); + + return new Date( + this.faker.datatype.number({ + min: startDate, + max: endDate, + }) + ); + } } diff --git a/test/date.spec.ts b/test/date.spec.ts index 4b3350a45a4..00227ae48cb 100644 --- a/test/date.spec.ts +++ b/test/date.spec.ts @@ -33,6 +33,15 @@ const seededRuns = [ context: 'Tuesday', abbr_context: 'Tue', }, + birthdate: { + noArgs: new Date('1943-05-01T16:57:03.433Z'), + ageMode: new Date('1943-05-01T16:57:03.433Z'), + ageRange: new Date('1942-07-31T22:01:09.077Z'), + age: new Date('1960-02-09T20:54:02.397Z'), + yearMode: new Date('1943-05-01T16:57:03.433Z'), + yearRange: new Date('1937-07-25T13:46:33.265Z'), + year: new Date('2000-02-09T20:54:02.397Z'), + }, }, }, { @@ -66,6 +75,15 @@ const seededRuns = [ context: 'Monday', abbr_context: 'Mon', }, + birthdate: { + noArgs: new Date('1936-05-09T16:21:36.994Z'), + ageMode: new Date('1936-05-09T16:21:36.994Z'), + ageRange: new Date('1935-10-31T03:23:57.685Z'), + age: new Date('1960-02-09T20:54:02.397Z'), + yearMode: new Date('1936-05-09T16:21:36.994Z'), + yearRange: new Date('1926-04-25T01:26:35.612Z'), + year: new Date('2000-02-09T20:54:02.397Z'), + }, }, }, { @@ -99,6 +117,15 @@ const seededRuns = [ context: 'Saturday', abbr_context: 'Sat', }, + birthdate: { + noArgs: new Date('1977-09-05T03:19:05.900Z'), + ageMode: new Date('1977-09-05T03:19:05.900Z'), + ageRange: new Date('1975-10-27T09:21:38.622Z'), + age: new Date('1960-02-09T20:54:02.397Z'), + yearMode: new Date('1977-09-05T03:19:05.900Z'), + yearRange: new Date('1992-12-17T03:22:58.630Z'), + year: new Date('2000-02-09T20:54:02.397Z'), + }, }, }, ]; @@ -361,6 +388,92 @@ describe('date', () => { expect(actual).toEqual(expectations.weekday.abbr_context); }); }); + + describe('birthdate()', () => { + it('should return deterministic value birthdate by default', () => { + faker.seed(seed); + + const actual = faker.date.birthdate({ + refDate: '2000-02-09T20:54:02.397Z', + }); + + expect(actual).toEqual(expectations.birthdate.noArgs); + }); + + it('should return deterministic value birthdate by age mode ', () => { + faker.seed(seed); + + const actual = faker.date.birthdate({ + mode: 'age', + refDate: '2000-02-09T20:54:02.397Z', + }); + + expect(actual).toEqual(expectations.birthdate.ageMode); + }); + + it('should return deterministic value birthdate by age range', () => { + faker.seed(seed); + + const actual = faker.date.birthdate({ + min: 20, + max: 80, + mode: 'age', + refDate: '2000-02-09T20:54:02.397Z', + }); + + expect(actual).toEqual(expectations.birthdate.ageRange); + }); + + it('should return deterministic value birthdate by age', () => { + faker.seed(seed); + + const actual = faker.date.birthdate({ + min: 40, + max: 40, + mode: 'age', + refDate: '2000-02-09T20:54:02.397Z', + }); + + expect(actual).toEqual(expectations.birthdate.age); + }); + + it('should return deterministic value birthdate by year mode', () => { + faker.seed(seed); + + const actual = faker.date.birthdate({ + mode: 'year', + refDate: '2000-02-09T20:54:02.397Z', + }); + + expect(actual).toEqual(expectations.birthdate.yearMode); + }); + + it('should return deterministic value birthdate by year range', () => { + faker.seed(seed); + + const actual = faker.date.birthdate({ + min: 1900, + max: 2000, + mode: 'year', + refDate: '2000-02-09T20:54:02.397Z', + }); + + expect(actual).toEqual(expectations.birthdate.yearRange); + }); + + it('should return deterministic value birthdate by year', () => { + faker.seed(seed); + + const actual = faker.date.birthdate({ + min: 2000, + max: 2000, + mode: 'year', + refDate: '2000-02-09T20:54:02.397Z', + }); + + expect(actual).toEqual(expectations.birthdate.year); + }); + }); }); } @@ -612,6 +725,66 @@ describe('date', () => { faker.definitions.date.weekday.abbr_context = backup_abbr_context; }); }); + + describe('birthdate', () => { + it('returns a random birthdate between two years', () => { + const min = 1990; + const max = 2000; + + const birthdate = faker.date.birthdate({ min: min, max: max }); + + // birthdate is a date object + expect(birthdate).to.be.an.instanceof(Date); + + // Generated date is between min and max + expect(birthdate.getFullYear()).to.be.at.least(min); + expect(birthdate.getFullYear()).to.be.at.most(max); + }); + + it('returns a random birthdate between two ages', () => { + const min = 4; + const max = 5; + + const birthdate = faker.date.birthdate({ min, max, mode: 'age' }); + + // birthdate is a date object + expect(birthdate).to.be.an.instanceof(Date); + + // Generated date is between min and max + expect(birthdate.getFullYear()).to.be.at.least( + new Date().getFullYear() - max + ); + expect(birthdate.getFullYear()).to.be.at.most( + new Date().getFullYear() - min + ); + }); + + it('returns a random birthdate', () => { + const birthdate = faker.date.birthdate(); + expect(birthdate).to.be.an.instanceof(Date); + }); + + it('returns a random birthdate between two ages', () => { + const min = 4; + const max = 5; + const now = new Date(); + const earlyBound = new Date(); + earlyBound.setFullYear(now.getFullYear() - max); + const lateBound = new Date(); + lateBound.setFullYear(now.getFullYear() - min); + + for (let i = 0; i < 10000; i++) { + const birthdate = faker.date.birthdate({ min, max, mode: 'age' }); + + // birthdate is a date object + expect(birthdate).to.be.an.instanceof(Date); + + // Generated date is between min and max + expect(birthdate).to.be.at.least(earlyBound); + expect(birthdate).to.be.at.most(lateBound); + } + }); + }); } }); }); From 1a175710de7fc7a02f251b70eecf32e91ae6977f Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sun, 1 May 2022 00:12:54 +0200 Subject: [PATCH 2/3] chore: apply suggestions/fixes Co-authored-by: Shinigami --- src/modules/date/index.ts | 74 +++++++++++++++---------------- test/date.spec.ts | 91 +++++++++++++++------------------------ 2 files changed, 73 insertions(+), 92 deletions(-) diff --git a/src/modules/date/index.ts b/src/modules/date/index.ts index 8eca4d52017..c4c29a30c43 100644 --- a/src/modules/date/index.ts +++ b/src/modules/date/index.ts @@ -1,5 +1,6 @@ import type { Faker } from '../..'; import type { DateEntryDefinition } from '../../definitions'; +import { FakerError } from '../../errors/faker-error'; /** * Converts date passed as a string, number or Date to a Date object. @@ -257,58 +258,59 @@ export class _Date { /** * Returns a random birthdate. * - * @param options The options to use to generate the birthdate. If no options are set, then an age between 18 and 80 (inclusive) is generated. + * @param options The options to use to generate the birthdate. If no options are set, an age between 18 and 80 (inclusive) is generated. * @param options.min The minimum age or year to generate a birthdate. * @param options.max The maximum age or year to generate a birthdate. - * @param options.refDate The date to use as reference point for the newly generated date. Defaults to now. - * @param options.mode The mode to generate the birthdate. Supported modes are 'year' and 'age'. + * @param options.refDate The date to use as reference point for the newly generated date. Defaults to `now`. + * @param options.mode The mode to generate the birthdate. Supported modes are `'age'` and `'year'` . * - * There are two modes available 'age' and 'year' (default). - * - 'age': The min and max options define the age of the person (e.g. 18 - 42) - * - 'year': The min and max options define the range the birthdate may be in. (e.g. 1900 - 2000) + * There are two modes available `'age'` and `'year'`: + * - `'age'`: The min and max options define the age of the person (e.g. `18` - `42`). + * - `'year'`: The min and max options define the range the birthdate may be in (e.g. `1900` - `2000`). + * + * Defaults to `year`. * * @example * faker.date.birthdate(); // 2016-08-12T03:24:00.000Z - * faker.date.birthdate({min: 18, max: 65, mode: 'age'}); // 1994-02-11T03:24:00.000Z - * faker.date.birthdate({min: 1900, max: 2000, mode: 'year'}); // 1995-02-11T03:24:00.000Z + * faker.date.birthdate({ min: 18, max: 65, mode: 'age' }); // 1994-02-11T03:24:00.000Z + * faker.date.birthdate({ min: 1900, max: 2000, mode: 'year' }); // 1995-02-11T03:24:00.000Z */ - birthdate(options?: { - min?: number; - max?: number; - mode?: 'age' | 'year'; - refDate?: string; - }): Date { - // Generate a random birthday date between two years, min and max - options = options || {}; - + birthdate( + options: { + min?: number; + max?: number; + mode?: 'age' | 'year'; + refDate?: string | Date | number; + } = {} + ): Date { const mode = options.mode === 'age' ? 'age' : 'year'; - let now = new Date(); - if (typeof options.refDate !== 'undefined') { - now = new Date(Date.parse(options.refDate)); - } - - let min = 0; - let max = 0; + const refDate = toDate(options.refDate); + const refYear = refDate.getUTCFullYear(); // If no min or max is specified, generate a random date between (now - 80) years and (now - 18) years respectively // So that people can still be considered as adults in most cases + // Convert to epoch timestamps + let min: number; + let max: number; if (mode === 'age') { - min = now.getFullYear() - (options.max ?? 80); - max = now.getFullYear() - (options.min ?? 18); + min = new Date(refDate).setUTCFullYear(refYear - (options.max ?? 80) - 1); + max = new Date(refDate).setUTCFullYear(refYear - (options.min ?? 18)); } else { - min = options.min ?? now.getFullYear() - 80; - max = options.max ?? now.getFullYear() - 18; + // Avoid generating dates the first and last date of the year + // to avoid running into other years depending on the timezone. + min = new Date(Date.UTC(0, 0, 2)).setUTCFullYear( + options.min ?? refYear - 80 + ); + max = new Date(Date.UTC(0, 11, 30)).setUTCFullYear( + options.max ?? refYear - 18 + ); } - const startDate = now.setFullYear(min); - const endDate = now.setFullYear(max); + if (max < min) { + throw new FakerError(`Max ${max} should be larger then min ${min}.`); + } - return new Date( - this.faker.datatype.number({ - min: startDate, - max: endDate, - }) - ); + return new Date(this.faker.datatype.number({ min, max })); } } diff --git a/test/date.spec.ts b/test/date.spec.ts index 00227ae48cb..228c7a06365 100644 --- a/test/date.spec.ts +++ b/test/date.spec.ts @@ -34,13 +34,13 @@ const seededRuns = [ abbr_context: 'Tue', }, birthdate: { - noArgs: new Date('1943-05-01T16:57:03.433Z'), - ageMode: new Date('1943-05-01T16:57:03.433Z'), - ageRange: new Date('1942-07-31T22:01:09.077Z'), - age: new Date('1960-02-09T20:54:02.397Z'), - yearMode: new Date('1943-05-01T16:57:03.433Z'), - yearRange: new Date('1937-07-25T13:46:33.265Z'), - year: new Date('2000-02-09T20:54:02.397Z'), + noArgs: new Date('1943-08-06T10:03:17.283Z'), + ageMode: new Date('1942-09-15T09:55:20.478Z'), + ageRange: new Date('1941-12-15T14:59:26.122Z'), + age: new Date('1959-06-26T13:52:19.442Z'), + yearMode: new Date('1943-08-06T10:03:17.283Z'), + yearRange: new Date('1937-10-30T15:52:07.381Z'), + year: new Date('2000-05-16T22:59:36.513Z'), }, }, }, @@ -76,13 +76,13 @@ const seededRuns = [ abbr_context: 'Mon', }, birthdate: { - noArgs: new Date('1936-05-09T16:21:36.994Z'), - ageMode: new Date('1936-05-09T16:21:36.994Z'), - ageRange: new Date('1935-10-31T03:23:57.685Z'), - age: new Date('1960-02-09T20:54:02.397Z'), - yearMode: new Date('1936-05-09T16:21:36.994Z'), - yearRange: new Date('1926-04-25T01:26:35.612Z'), - year: new Date('2000-02-09T20:54:02.397Z'), + noArgs: new Date('1936-07-04T15:55:47.989Z'), + ageMode: new Date('1935-08-14T07:41:47.183Z'), + ageRange: new Date('1935-02-03T18:44:07.874Z'), + age: new Date('1959-05-16T12:14:12.585Z'), + yearMode: new Date('1936-07-04T15:55:47.989Z'), + yearRange: new Date('1926-06-20T07:18:05.539Z'), + year: new Date('2000-04-06T02:45:32.324Z'), }, }, }, @@ -118,13 +118,13 @@ const seededRuns = [ abbr_context: 'Sat', }, birthdate: { - noArgs: new Date('1977-09-05T03:19:05.900Z'), - ageMode: new Date('1977-09-05T03:19:05.900Z'), - ageRange: new Date('1975-10-27T09:21:38.622Z'), - age: new Date('1960-02-09T20:54:02.397Z'), - yearMode: new Date('1977-09-05T03:19:05.900Z'), - yearRange: new Date('1992-12-17T03:22:58.630Z'), - year: new Date('2000-02-09T20:54:02.397Z'), + noArgs: new Date('1978-06-29T09:24:02.647Z'), + ageMode: new Date('1977-08-10T01:09:17.468Z'), + ageRange: new Date('1975-10-01T07:11:50.190Z'), + age: new Date('1960-01-14T18:44:13.966Z'), + yearMode: new Date('1978-06-29T09:24:02.647Z'), + yearRange: new Date('1993-10-11T07:44:59.519Z'), + year: new Date('2000-12-04T01:16:03.286Z'), }, }, }, @@ -727,18 +727,23 @@ describe('date', () => { }); describe('birthdate', () => { + it('returns a random birthdate', () => { + const birthdate = faker.date.birthdate(); + expect(birthdate).toBeInstanceOf(Date); + }); + it('returns a random birthdate between two years', () => { const min = 1990; const max = 2000; - const birthdate = faker.date.birthdate({ min: min, max: max }); + const birthdate = faker.date.birthdate({ min, max, mode: 'year' }); // birthdate is a date object - expect(birthdate).to.be.an.instanceof(Date); + expect(birthdate).toBeInstanceOf(Date); // Generated date is between min and max - expect(birthdate.getFullYear()).to.be.at.least(min); - expect(birthdate.getFullYear()).to.be.at.most(max); + expect(birthdate.getUTCFullYear()).toBeGreaterThanOrEqual(min); + expect(birthdate.getUTCFullYear()).toBeLessThanOrEqual(max); }); it('returns a random birthdate between two ages', () => { @@ -748,42 +753,16 @@ describe('date', () => { const birthdate = faker.date.birthdate({ min, max, mode: 'age' }); // birthdate is a date object - expect(birthdate).to.be.an.instanceof(Date); + expect(birthdate).toBeInstanceOf(Date); // Generated date is between min and max - expect(birthdate.getFullYear()).to.be.at.least( - new Date().getFullYear() - max + expect(birthdate.getUTCFullYear()).toBeGreaterThanOrEqual( + new Date().getUTCFullYear() - max - 1 ); - expect(birthdate.getFullYear()).to.be.at.most( - new Date().getFullYear() - min + expect(birthdate.getUTCFullYear()).toBeLessThanOrEqual( + new Date().getUTCFullYear() - min ); }); - - it('returns a random birthdate', () => { - const birthdate = faker.date.birthdate(); - expect(birthdate).to.be.an.instanceof(Date); - }); - - it('returns a random birthdate between two ages', () => { - const min = 4; - const max = 5; - const now = new Date(); - const earlyBound = new Date(); - earlyBound.setFullYear(now.getFullYear() - max); - const lateBound = new Date(); - lateBound.setFullYear(now.getFullYear() - min); - - for (let i = 0; i < 10000; i++) { - const birthdate = faker.date.birthdate({ min, max, mode: 'age' }); - - // birthdate is a date object - expect(birthdate).to.be.an.instanceof(Date); - - // Generated date is between min and max - expect(birthdate).to.be.at.least(earlyBound); - expect(birthdate).to.be.at.most(lateBound); - } - }); }); } }); From 7c302b2339660f8e94e64636a9e414d9d4a6b215 Mon Sep 17 00:00:00 2001 From: ST-DDT Date: Sun, 15 May 2022 17:18:10 +0200 Subject: [PATCH 3/3] chore: update examples --- src/modules/date/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/date/index.ts b/src/modules/date/index.ts index c4c29a30c43..0ff90db1c81 100644 --- a/src/modules/date/index.ts +++ b/src/modules/date/index.ts @@ -271,9 +271,9 @@ export class _Date { * Defaults to `year`. * * @example - * faker.date.birthdate(); // 2016-08-12T03:24:00.000Z - * faker.date.birthdate({ min: 18, max: 65, mode: 'age' }); // 1994-02-11T03:24:00.000Z - * faker.date.birthdate({ min: 1900, max: 2000, mode: 'year' }); // 1995-02-11T03:24:00.000Z + * faker.date.birthdate() // 1977-07-10T01:37:30.719Z + * faker.date.birthdate({ min: 18, max: 65, mode: 'age' }) // 2003-11-02T20:03:20.116Z + * faker.date.birthdate({ min: 1900, max: 2000, mode: 'year' }) // 1940-08-20T08:53:07.538Z */ birthdate( options: {