Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(date)!: fail on invalid dates #2757

Merged
merged 11 commits into from
Mar 24, 2024
9 changes: 9 additions & 0 deletions docs/guide/upgrading_v9/2757.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
### Fail on invalid dates

Various methods in the `faker.date` module allow you to pass a `Date`-ish value:
that is, either a Javascript Date, or a timestamp number or string that can be converted to a `Date` via the `new Date()` constructor.

Previously, if you passed something which could not be parsed to a `Date`, it would fall back to the current reference date.
Now, this throws an error raising awareness of that bad value.

This affects the `refDate` parameter of the `anytime()`, `birthdate()`, `past()`, `future()`, `recent()` and `soon()`, methods as well as the `from` and `to` parameters of `between()` and `betweens()`.
115 changes: 43 additions & 72 deletions src/modules/date/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,19 @@ import { SimpleModuleBase } from '../../internal/module-base';
import { assertLocaleData } from '../../locale-proxy';

/**
* Converts date passed as a string, number or Date to a Date object.
* Converts date passed as a string, number or Date to a valid Date object.
* If nothing or a non-parsable value is passed, then it will take the value from the given fallback.
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
*
* @param date The date to convert.
* @param fallback The fallback date to use if the passed date is not valid.
* @param name The parameter name of the date.
*/
function toDate(
date: string | Date | number | undefined,
fallback: () => Date
): Date {
if (date == null) {
return fallback();
function toDate(date: string | Date | number, name: string = 'refDate'): Date {
const converted = new Date(date);
if (Number.isNaN(converted.valueOf())) {
throw new FakerError(`Invalid ${name} date: ${date.toString()}`);
}

date = new Date(date);
if (Number.isNaN(date.valueOf())) {
date = fallback();
}

return date;
return converted;
}

/**
Expand Down Expand Up @@ -56,13 +49,12 @@ export class SimpleDateModule extends SimpleModuleBase {
refDate?: string | Date | number;
} = {}
): Date {
const { refDate } = options;

const date = toDate(refDate, this.faker.defaultRefDate);
const { refDate = this.faker.defaultRefDate() } = options;
const time = toDate(refDate).getTime();

return this.between({
from: new Date(date.getTime() - 1000 * 60 * 60 * 24 * 365),
to: new Date(date.getTime() + 1000 * 60 * 60 * 24 * 365),
from: time - 1000 * 60 * 60 * 24 * 365,
to: time + 1000 * 60 * 60 * 24 * 365,
});
}

Expand Down Expand Up @@ -98,23 +90,18 @@ export class SimpleDateModule extends SimpleModuleBase {
refDate?: string | Date | number;
} = {}
): Date {
const { years = 1, refDate } = options;
const { years = 1, refDate = this.faker.defaultRefDate() } = options;

if (years <= 0) {
throw new FakerError('Years must be greater than 0.');
}

const date = toDate(refDate, this.faker.defaultRefDate);
const range = {
min: 1000,
max: years * 365 * 24 * 3600 * 1000,
};

let past = date.getTime();
past -= this.faker.number.int(range); // some time from now to N years ago, in milliseconds
date.setTime(past);
const time = toDate(refDate).getTime();

return date;
return this.between({
from: time - years * 365 * 24 * 3600 * 1000,
to: time - 1000,
});
}

/**
Expand Down Expand Up @@ -149,23 +136,18 @@ export class SimpleDateModule extends SimpleModuleBase {
refDate?: string | Date | number;
} = {}
): Date {
const { years = 1, refDate } = options;
const { years = 1, refDate = this.faker.defaultRefDate() } = options;

if (years <= 0) {
throw new FakerError('Years must be greater than 0.');
}

const date = toDate(refDate, this.faker.defaultRefDate);
const range = {
min: 1000,
max: years * 365 * 24 * 3600 * 1000,
};
const time = toDate(refDate).getTime();

let future = date.getTime();
future += this.faker.number.int(range); // some time from now to N years later, in milliseconds
date.setTime(future);

return date;
return this.between({
from: time + 1000,
to: time + years * 365 * 24 * 3600 * 1000,
});
}

/**
Expand All @@ -192,11 +174,10 @@ export class SimpleDateModule extends SimpleModuleBase {
}): Date {
const { from, to } = options;

const fromMs = toDate(from, this.faker.defaultRefDate).getTime();
const toMs = toDate(to, this.faker.defaultRefDate).getTime();
const dateOffset = this.faker.number.int(toMs - fromMs);
const fromMs = toDate(from, 'from').getTime();
const toMs = toDate(to, 'to').getTime();

return new Date(fromMs + dateOffset);
return new Date(this.faker.number.int({ min: fromMs, max: toMs }));
}

/**
Expand Down Expand Up @@ -291,23 +272,18 @@ export class SimpleDateModule extends SimpleModuleBase {
refDate?: string | Date | number;
} = {}
): Date {
const { days = 1, refDate } = options;
const { days = 1, refDate = this.faker.defaultRefDate() } = options;

if (days <= 0) {
throw new FakerError('Days must be greater than 0.');
}

const date = toDate(refDate, this.faker.defaultRefDate);
const range = {
min: 1000,
max: days * 24 * 3600 * 1000,
};

let future = date.getTime();
future -= this.faker.number.int(range); // some time from now to N days ago, in milliseconds
date.setTime(future);
const time = toDate(refDate).getTime();

return date;
return this.between({
from: time - days * 24 * 3600 * 1000,
to: time - 1000,
});
}

/**
Expand Down Expand Up @@ -342,23 +318,18 @@ export class SimpleDateModule extends SimpleModuleBase {
refDate?: string | Date | number;
} = {}
): Date {
const { days = 1, refDate } = options;
const { days = 1, refDate = this.faker.defaultRefDate() } = options;

if (days <= 0) {
throw new FakerError('Days must be greater than 0.');
}

const date = toDate(refDate, this.faker.defaultRefDate);
const range = {
min: 1000,
max: days * 24 * 3600 * 1000,
};
const time = toDate(refDate).getTime();

let future = date.getTime();
future += this.faker.number.int(range); // some time from now to N days later, in milliseconds
date.setTime(future);

return date;
return this.between({
from: time + 1000,
to: time + days * 24 * 3600 * 1000,
});
}

/**
Expand Down Expand Up @@ -415,9 +386,9 @@ export class SimpleDateModule extends SimpleModuleBase {
refDate?: string | Date | number;
} = {}
): Date {
const mode = options.mode === 'age' ? 'age' : 'year';
const refDate = toDate(options.refDate, this.faker.defaultRefDate);
const refYear = refDate.getUTCFullYear();
const { mode = 'year', refDate = this.faker.defaultRefDate() } = options;
const date = toDate(refDate);
const refYear = date.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
Expand All @@ -426,8 +397,8 @@ export class SimpleDateModule extends SimpleModuleBase {
let min: number;
let max: number;
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
if (mode === 'age') {
min = new Date(refDate).setUTCFullYear(refYear - (options.max ?? 80) - 1);
max = new Date(refDate).setUTCFullYear(refYear - (options.min ?? 18));
min = new Date(date).setUTCFullYear(refYear - (options.max ?? 80) - 1);
max = new Date(date).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.
Expand Down
48 changes: 24 additions & 24 deletions test/modules/__snapshots__/date.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,21 @@ exports[`date > 42 > month > with abbreviated = true and context = true 1`] = `"

exports[`date > 42 > month > with context = true 1`] = `"May"`;

exports[`date > 42 > past > with only Date refDate 1`] = `2020-10-08T00:10:57.898Z`;
exports[`date > 42 > past > with only Date refDate 1`] = `2020-07-08T10:07:32.524Z`;

exports[`date > 42 > past > with only number refDate 1`] = `2020-10-08T00:10:57.898Z`;
exports[`date > 42 > past > with only number refDate 1`] = `2020-07-08T10:07:32.524Z`;

exports[`date > 42 > past > with only string refDate 1`] = `2020-10-08T00:10:57.898Z`;
exports[`date > 42 > past > with only string refDate 1`] = `2020-07-08T10:07:32.524Z`;

exports[`date > 42 > past > with value 1`] = `2017-05-26T15:26:23.206Z`;
exports[`date > 42 > past > with value 1`] = `2014-11-22T18:52:07.216Z`;

exports[`date > 42 > recent > with only Date refDate 1`] = `2021-02-21T08:09:54.819Z`;
exports[`date > 42 > recent > with only Date refDate 1`] = `2021-02-21T02:08:35.603Z`;

exports[`date > 42 > recent > with only number refDate 1`] = `2021-02-21T08:09:54.819Z`;
exports[`date > 42 > recent > with only number refDate 1`] = `2021-02-21T02:08:35.603Z`;

exports[`date > 42 > recent > with only string refDate 1`] = `2021-02-21T08:09:54.819Z`;
exports[`date > 42 > recent > with only string refDate 1`] = `2021-02-21T02:08:35.603Z`;

exports[`date > 42 > recent > with value 1`] = `2021-02-17T23:15:52.423Z`;
exports[`date > 42 > recent > with value 1`] = `2021-02-15T11:02:37.999Z`;

exports[`date > 42 > soon > with only Date refDate 1`] = `2021-02-22T02:08:36.603Z`;

Expand Down Expand Up @@ -223,21 +223,21 @@ exports[`date > 1211 > month > with abbreviated = true and context = true 1`] =

exports[`date > 1211 > month > with context = true 1`] = `"December"`;

exports[`date > 1211 > past > with only Date refDate 1`] = `2020-03-19T19:19:04.066Z`;
exports[`date > 1211 > past > with only Date refDate 1`] = `2021-01-26T14:59:26.356Z`;

exports[`date > 1211 > past > with only number refDate 1`] = `2020-03-19T19:19:04.066Z`;
exports[`date > 1211 > past > with only number refDate 1`] = `2021-01-26T14:59:26.356Z`;

exports[`date > 1211 > past > with only string refDate 1`] = `2020-03-19T19:19:04.066Z`;
exports[`date > 1211 > past > with only string refDate 1`] = `2021-01-26T14:59:26.356Z`;

exports[`date > 1211 > past > with value 1`] = `2011-11-12T14:47:19.904Z`;
exports[`date > 1211 > past > with value 1`] = `2020-06-05T19:31:10.518Z`;

exports[`date > 1211 > recent > with only Date refDate 1`] = `2021-02-20T18:52:11.498Z`;
exports[`date > 1211 > recent > with only Date refDate 1`] = `2021-02-21T15:26:18.924Z`;

exports[`date > 1211 > recent > with only number refDate 1`] = `2021-02-20T18:52:11.498Z`;
exports[`date > 1211 > recent > with only number refDate 1`] = `2021-02-21T15:26:18.924Z`;

exports[`date > 1211 > recent > with only string refDate 1`] = `2021-02-20T18:52:11.498Z`;
exports[`date > 1211 > recent > with only string refDate 1`] = `2021-02-21T15:26:18.924Z`;

exports[`date > 1211 > recent > with value 1`] = `2021-02-12T10:18:34.226Z`;
exports[`date > 1211 > recent > with value 1`] = `2021-02-20T23:59:56.196Z`;

exports[`date > 1211 > soon > with only Date refDate 1`] = `2021-02-22T15:26:19.924Z`;

Expand Down Expand Up @@ -349,21 +349,21 @@ exports[`date > 1337 > month > with abbreviated = true and context = true 1`] =

exports[`date > 1337 > month > with context = true 1`] = `"April"`;

exports[`date > 1337 > past > with only Date refDate 1`] = `2020-11-18T01:49:04.822Z`;
exports[`date > 1337 > past > with only Date refDate 1`] = `2020-05-28T08:29:25.600Z`;

exports[`date > 1337 > past > with only number refDate 1`] = `2020-11-18T01:49:04.822Z`;
exports[`date > 1337 > past > with only number refDate 1`] = `2020-05-28T08:29:25.600Z`;

exports[`date > 1337 > past > with only string refDate 1`] = `2020-11-18T01:49:04.822Z`;
exports[`date > 1337 > past > with only string refDate 1`] = `2020-05-28T08:29:25.600Z`;

exports[`date > 1337 > past > with value 1`] = `2018-07-11T07:47:33.460Z`;
exports[`date > 1337 > past > with value 1`] = `2013-10-08T02:30:56.962Z`;

exports[`date > 1337 > recent > with only Date refDate 1`] = `2021-02-21T10:51:56.041Z`;
exports[`date > 1337 > recent > with only Date refDate 1`] = `2021-02-20T23:26:34.381Z`;

exports[`date > 1337 > recent > with only number refDate 1`] = `2021-02-21T10:51:56.041Z`;
exports[`date > 1337 > recent > with only number refDate 1`] = `2021-02-20T23:26:34.381Z`;

exports[`date > 1337 > recent > with only string refDate 1`] = `2021-02-21T10:51:56.041Z`;
exports[`date > 1337 > recent > with only string refDate 1`] = `2021-02-20T23:26:34.381Z`;

exports[`date > 1337 > recent > with value 1`] = `2021-02-19T02:16:05.654Z`;
exports[`date > 1337 > recent > with value 1`] = `2021-02-14T08:02:24.768Z`;

exports[`date > 1337 > soon > with only Date refDate 1`] = `2021-02-21T23:26:35.381Z`;

Expand Down
Loading