Skip to content

Commit b498d1f

Browse files
authored
refactor(date)!: birthdate improvements (#2756)
1 parent 6191a5d commit b498d1f

4 files changed

Lines changed: 257 additions & 131 deletions

File tree

docs/guide/upgrading_v9/2756.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
### Changed default mode from birthdate
2+
3+
Previously, the method had defaults that were unclear in their specific impact.
4+
Now, the method requires either none or all of the `min`, `max` and `mode` options.
5+
6+
We also improved the error messages in case of invalid min/max age/year ranges.

src/modules/date/index.ts

Lines changed: 173 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -357,90 +357,199 @@ export class SimpleDateModule extends SimpleModuleBase {
357357
}
358358

359359
/**
360-
* Returns a random birthdate.
360+
* Returns a random birthdate. By default, the birthdate is generated for an adult between 18 and 80 years old.
361+
* But you can customize the `'age'` range or the `'year'` range to generate a more specific birthdate.
361362
*
362-
* @param options The options to use to generate the birthdate. If no options are set, an age between 18 and 80 (inclusive) is generated.
363-
* @param options.min The minimum age or year to generate a birthdate.
364-
* @param options.max The maximum age or year to generate a birthdate.
365-
* @param options.refDate The date to use as reference point for the newly generated date. Defaults to `now`.
366-
* @param options.mode The mode to generate the birthdate. Supported modes are `'age'` and `'year'` .
363+
* @param options The options to use to generate the birthdate.
364+
* @param options.refDate The date to use as reference point for the newly generated date. Defaults to `faker.defaultRefDate()`.
365+
*
366+
* @example
367+
* faker.date.birthdate() // 1977-07-10T01:37:30.719Z
368+
*
369+
* @since 7.0.0
370+
*/
371+
birthdate(options?: {
372+
/**
373+
* The date to use as reference point for the newly generated date.
374+
*
375+
* @default faker.defaultRefDate()
376+
*/
377+
refDate?: string | Date | number;
378+
}): Date;
379+
/**
380+
* Returns a random birthdate for a given age range.
381+
*
382+
* @param options The options to use to generate the birthdate.
383+
* @param options.mode `'age'` to generate a birthdate based on the age range. It is also possible to generate a birthdate based on a `'year'` range.
384+
* @param options.min The minimum age to generate a birthdate for.
385+
* @param options.max The maximum age to generate a birthdate for.
386+
* @param options.refDate The date to use as reference point for the newly generated date. Defaults to `faker.defaultRefDate()`.
387+
*
388+
* @example
389+
* faker.date.birthdate({ mode: 'age', min: 18, max: 65 }) // 2003-11-02T20:03:20.116Z
367390
*
368-
* There are two modes available `'age'` and `'year'`:
369-
* - `'age'`: The min and max options define the age of the person (e.g. `18` - `42`).
370-
* - `'year'`: The min and max options define the range the birthdate may be in (e.g. `1900` - `2000`).
391+
* @since 7.0.0
392+
*/
393+
birthdate(options: {
394+
/**
395+
* `'age'` to generate a birthdate based on the age range.
396+
* It is also possible to generate a birthdate based on a `'year'` range.
397+
*/
398+
mode: 'age';
399+
/**
400+
* The minimum age to generate a birthdate for.
401+
*/
402+
min: number;
403+
/**
404+
* The maximum age to generate a birthdate for.
405+
*/
406+
max: number;
407+
/**
408+
* The date to use as reference point for the newly generated date.
409+
*
410+
* @default faker.defaultRefDate()
411+
*/
412+
refDate?: string | Date | number;
413+
}): Date;
414+
/**
415+
* Returns a random birthdate in the given range of years.
416+
*
417+
* @param options The options to use to generate the birthdate.
418+
* @param options.mode `'year'` to generate a birthdate based on the year range. It is also possible to generate a birthdate based on a `'age'` range.
419+
* @param options.min The minimum year to generate a birthdate in.
420+
* @param options.max The maximum year to generate a birthdate in.
421+
*
422+
* @example
423+
* faker.date.birthdate({ mode: 'year', min: 1900, max: 2000 }) // 1940-08-20T08:53:07.538Z
424+
*
425+
* @since 7.0.0
426+
*/
427+
birthdate(options: {
428+
/**
429+
* `'year'` to generate a birthdate based on the year range.
430+
* It is also possible to generate a birthdate based on an `'age'` range.
431+
*/
432+
mode: 'year';
433+
/**
434+
* The minimum year to generate a birthdate in.
435+
*/
436+
min: number;
437+
/**
438+
* The maximum year to generate a birthdate in.
439+
*/
440+
max: number;
441+
}): Date;
442+
/**
443+
* Returns a random birthdate. By default, the birthdate is generated for an adult between 18 and 80 years old.
444+
* But you can customize the `'age'` range or the `'year'` range to generate a more specific birthdate.
371445
*
372-
* Defaults to `year`.
446+
* @param options The options to use to generate the birthdate.
447+
* @param options.mode Either `'age'` or `'year'` to generate a birthdate based on the age or year range.
448+
* @param options.min The minimum age or year to generate a birthdate in.
449+
* @param options.max The maximum age or year to generate a birthdate in.
450+
* @param options.refDate The date to use as reference point for the newly generated date.
451+
* Only used when `mode` is `'age'`.
452+
* Defaults to `faker.defaultRefDate()`.
373453
*
374454
* @example
375455
* faker.date.birthdate() // 1977-07-10T01:37:30.719Z
376-
* faker.date.birthdate({ min: 18, max: 65, mode: 'age' }) // 2003-11-02T20:03:20.116Z
377-
* faker.date.birthdate({ min: 1900, max: 2000, mode: 'year' }) // 1940-08-20T08:53:07.538Z
456+
* faker.date.birthdate({ mode: 'age', min: 18, max: 65 }) // 2003-11-02T20:03:20.116Z
457+
* faker.date.birthdate({ mode: 'year', min: 1900, max: 2000 }) // 1940-08-20T08:53:07.538Z
378458
*
379459
* @since 7.0.0
380460
*/
461+
birthdate(
462+
options?:
463+
| {
464+
/**
465+
* The date to use as reference point for the newly generated date.
466+
*
467+
* @default faker.defaultRefDate()
468+
*/
469+
refDate?: string | Date | number;
470+
}
471+
| {
472+
/**
473+
* Either `'age'` or `'year'` to generate a birthdate based on the age or year range.
474+
*/
475+
mode: 'age' | 'year';
476+
/**
477+
* The minimum age/year to generate a birthdate for/in.
478+
*/
479+
min: number;
480+
/**
481+
* The maximum age/year to generate a birthdate for/in.
482+
*/
483+
max: number;
484+
/**
485+
* The date to use as reference point for the newly generated date.
486+
* Only used when `mode` is `'age'`.
487+
*
488+
* @default faker.defaultRefDate()
489+
*/
490+
refDate?: string | Date | number;
491+
}
492+
): Date;
381493
birthdate(
382494
options: {
383-
/**
384-
* The minimum age or year to generate a birthdate.
385-
*
386-
* @default 18
387-
*/
495+
mode?: 'age' | 'year';
388496
min?: number;
389-
/**
390-
* The maximum age or year to generate a birthdate.
391-
*
392-
* @default 80
393-
*/
394497
max?: number;
395-
/**
396-
* The mode to generate the birthdate. Supported modes are `'age'` and `'year'` .
397-
*
398-
* There are two modes available `'age'` and `'year'`:
399-
* - `'age'`: The min and max options define the age of the person (e.g. `18` - `42`).
400-
* - `'year'`: The min and max options define the range the birthdate may be in (e.g. `1900` - `2000`).
401-
*
402-
* @default 'year'
403-
*/
404-
mode?: 'age' | 'year';
405-
/**
406-
* The date to use as reference point for the newly generated date.
407-
*
408-
* @default faker.defaultRefDate()
409-
*/
410498
refDate?: string | Date | number;
411499
} = {}
412500
): Date {
413-
const { mode = 'year', refDate = this.faker.defaultRefDate() } = options;
414-
const date = toDate(refDate);
415-
const refYear = date.getUTCFullYear();
416-
417-
// If no min or max is specified, generate a random date between (now - 80) years and (now - 18) years respectively
418-
// So that people can still be considered as adults in most cases
419-
420-
// Convert to epoch timestamps
421-
let min: number;
422-
let max: number;
423-
if (mode === 'age') {
424-
min = new Date(date).setUTCFullYear(refYear - (options.max ?? 80) - 1);
425-
max = new Date(date).setUTCFullYear(refYear - (options.min ?? 18));
426-
} else {
427-
// Avoid generating dates the first and last date of the year
428-
// to avoid running into other years depending on the timezone.
429-
min = new Date(Date.UTC(0, 0, 2)).setUTCFullYear(
430-
options.min ?? refYear - 80
431-
);
432-
max = new Date(Date.UTC(0, 11, 30)).setUTCFullYear(
433-
options.max ?? refYear - 19
434-
);
435-
}
436-
437-
if (max < min) {
501+
const {
502+
mode = 'age',
503+
min = 18,
504+
max = 80,
505+
refDate: rawRefDate = this.faker.defaultRefDate(),
506+
mode: originalMode,
507+
min: originalMin,
508+
max: originalMax,
509+
} = options;
510+
511+
// TODO @ST-DDT 2024-03-17: Remove check in v10
512+
const optionsSet = [originalMin, originalMax, originalMode].filter(
513+
(x) => x != null
514+
).length;
515+
if (optionsSet % 3 !== 0) {
438516
throw new FakerError(
439-
`Max ${options.max} should be larger than or equal to min ${options.min}.`
517+
"The 'min', 'max', and 'mode' options must be set together."
440518
);
441519
}
442520

443-
return new Date(this.faker.number.int({ min, max }));
521+
const refDate = toDate(rawRefDate);
522+
const refYear = refDate.getUTCFullYear();
523+
524+
switch (mode) {
525+
case 'age': {
526+
const from = new Date(refDate).setUTCFullYear(refYear - max - 1);
527+
const to = new Date(refDate).setUTCFullYear(refYear - min);
528+
529+
if (from > to) {
530+
throw new FakerError(
531+
`Max age ${max} should be greater than or equal to min age ${min}.`
532+
);
533+
}
534+
535+
return this.between({ from, to });
536+
}
537+
538+
case 'year': {
539+
// Avoid generating dates on the first and last date of the year
540+
// to avoid running into other years depending on the timezone.
541+
const from = new Date(Date.UTC(0, 0, 2)).setUTCFullYear(min);
542+
const to = new Date(Date.UTC(0, 11, 30)).setUTCFullYear(max);
543+
544+
if (from > to) {
545+
throw new FakerError(
546+
`Max year ${max} should be greater than or equal to min year ${min}.`
547+
);
548+
}
549+
550+
return this.between({ from, to });
551+
}
552+
}
444553
}
445554
}
446555

test/modules/__snapshots__/date.spec.ts.snap

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -67,17 +67,13 @@ exports[`date > 42 > betweens > with string dates and count 1`] = `
6767

6868
exports[`date > 42 > birthdate > with age and refDate 1`] = `1980-07-07T19:06:53.165Z`;
6969

70-
exports[`date > 42 > birthdate > with age mode and refDate 1`] = `1963-09-27T06:10:42.813Z`;
71-
7270
exports[`date > 42 > birthdate > with age range and refDate 1`] = `1962-12-27T20:14:08.437Z`;
7371

74-
exports[`date > 42 > birthdate > with only refDate 1`] = `1964-03-22T08:05:48.849Z`;
75-
76-
exports[`date > 42 > birthdate > with year and refDate 1`] = `0020-07-07T19:06:53.165Z`;
72+
exports[`date > 42 > birthdate > with only refDate 1`] = `1963-09-27T06:10:42.813Z`;
7773

78-
exports[`date > 42 > birthdate > with year mode and refDate 1`] = `1964-03-22T08:05:48.849Z`;
74+
exports[`date > 42 > birthdate > with year 1`] = `2000-05-16T22:59:36.655Z`;
7975

80-
exports[`date > 42 > birthdate > with year range and refDate 1`] = `0057-12-20T11:59:38.353Z`;
76+
exports[`date > 42 > birthdate > with year range 1`] = `1937-10-30T15:52:21.843Z`;
8177

8278
exports[`date > 42 > future > with only Date refDate 1`] = `2021-07-08T10:07:33.524Z`;
8379

@@ -195,17 +191,13 @@ exports[`date > 1211 > betweens > with string dates and count 1`] = `
195191

196192
exports[`date > 1211 > birthdate > with age and refDate 1`] = `1981-01-26T13:16:31.426Z`;
197193

198-
exports[`date > 1211 > birthdate > with age mode and refDate 1`] = `1998-08-21T21:24:31.101Z`;
199-
200194
exports[`date > 1211 > birthdate > with age range and refDate 1`] = `1996-10-13T01:44:07.954Z`;
201195

202-
exports[`date > 1211 > birthdate > with only refDate 1`] = `1998-07-25T13:16:47.251Z`;
196+
exports[`date > 1211 > birthdate > with only refDate 1`] = `1998-08-21T21:24:31.101Z`;
203197

204-
exports[`date > 1211 > birthdate > with year and refDate 1`] = `0021-01-26T13:16:31.426Z`;
198+
exports[`date > 1211 > birthdate > with year 1`] = `2000-12-04T01:16:03.291Z`;
205199

206-
exports[`date > 1211 > birthdate > with year mode and refDate 1`] = `1998-07-25T13:16:47.251Z`;
207-
208-
exports[`date > 1211 > birthdate > with year range and refDate 1`] = `0113-12-03T19:45:28.165Z`;
200+
exports[`date > 1211 > birthdate > with year range 1`] = `1993-10-11T07:45:00.030Z`;
209201

210202
exports[`date > 1211 > future > with only Date refDate 1`] = `2022-01-26T14:59:27.356Z`;
211203

@@ -321,17 +313,13 @@ exports[`date > 1337 > betweens > with string dates and count 1`] = `
321313

322314
exports[`date > 1337 > birthdate > with age and refDate 1`] = `1980-05-27T14:46:44.794Z`;
323315

324-
exports[`date > 1337 > birthdate > with age mode and refDate 1`] = `1956-08-25T03:56:58.153Z`;
325-
326316
exports[`date > 1337 > birthdate > with age range and refDate 1`] = `1956-02-15T21:16:37.850Z`;
327317

328-
exports[`date > 1337 > birthdate > with only refDate 1`] = `1957-03-31T18:18:16.563Z`;
329-
330-
exports[`date > 1337 > birthdate > with year and refDate 1`] = `0020-05-27T14:46:44.794Z`;
318+
exports[`date > 1337 > birthdate > with only refDate 1`] = `1956-08-25T03:56:58.153Z`;
331319

332-
exports[`date > 1337 > birthdate > with year mode and refDate 1`] = `1957-03-31T18:18:16.563Z`;
320+
exports[`date > 1337 > birthdate > with year 1`] = `2000-04-06T02:45:32.287Z`;
333321

334-
exports[`date > 1337 > birthdate > with year range and refDate 1`] = `0046-08-09T19:19:14.289Z`;
322+
exports[`date > 1337 > birthdate > with year range 1`] = `1926-06-20T07:18:01.782Z`;
335323

336324
exports[`date > 1337 > future > with only Date refDate 1`] = `2021-05-28T08:29:26.600Z`;
337325

0 commit comments

Comments
 (0)