diff --git a/src/modules/date/index.ts b/src/modules/date/index.ts index 09f608f0490..0ff90db1c81 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. @@ -253,4 +254,63 @@ 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, 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 `'age'` and `'year'` . + * + * 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() // 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: { + min?: number; + max?: number; + mode?: 'age' | 'year'; + refDate?: string | Date | number; + } = {} + ): Date { + const mode = options.mode === 'age' ? 'age' : 'year'; + 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 = new Date(refDate).setUTCFullYear(refYear - (options.max ?? 80) - 1); + max = new Date(refDate).setUTCFullYear(refYear - (options.min ?? 18)); + } else { + // 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 + ); + } + + if (max < min) { + throw new FakerError(`Max ${max} should be larger then min ${min}.`); + } + + return new Date(this.faker.datatype.number({ min, max })); + } } diff --git a/test/date.spec.ts b/test/date.spec.ts index 4b3350a45a4..228c7a06365 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-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'), + }, }, }, { @@ -66,6 +75,15 @@ const seededRuns = [ context: 'Monday', abbr_context: 'Mon', }, + birthdate: { + 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'), + }, }, }, { @@ -99,6 +117,15 @@ const seededRuns = [ context: 'Saturday', abbr_context: 'Sat', }, + birthdate: { + 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'), + }, }, }, ]; @@ -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,45 @@ describe('date', () => { faker.definitions.date.weekday.abbr_context = backup_abbr_context; }); }); + + 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, max, mode: 'year' }); + + // birthdate is a date object + expect(birthdate).toBeInstanceOf(Date); + + // Generated date is between min and max + expect(birthdate.getUTCFullYear()).toBeGreaterThanOrEqual(min); + expect(birthdate.getUTCFullYear()).toBeLessThanOrEqual(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).toBeInstanceOf(Date); + + // Generated date is between min and max + expect(birthdate.getUTCFullYear()).toBeGreaterThanOrEqual( + new Date().getUTCFullYear() - max - 1 + ); + expect(birthdate.getUTCFullYear()).toBeLessThanOrEqual( + new Date().getUTCFullYear() - min + ); + }); + }); } }); });