Skip to content

Commit 237e7dc

Browse files
feat(date): add ability to provide year range for past and future (#3783)
Co-authored-by: ST-DDT <ST-DDT@gmx.de>
1 parent f4a1c3e commit 237e7dc

3 files changed

Lines changed: 201 additions & 21 deletions

File tree

src/modules/date/index.ts

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ import { toDate } from '../../internal/date';
55
import { assertLocaleData } from '../../internal/locale-proxy';
66
import { SimpleModuleBase } from '../../internal/module-base';
77

8+
/**
9+
* Small helper function to convert a number of years to an amount of milliseconds.
10+
*
11+
* @param years The number of years to convert to milliseconds.
12+
*/
13+
function yearsToMs(years: number): number {
14+
return years * 365 * 24 * 3600 * 1000;
15+
}
16+
817
/**
918
* Module to generate dates (without methods requiring localized data).
1019
*/
@@ -47,14 +56,18 @@ export class SimpleDateModule extends SimpleModuleBase {
4756
* Generates a random date in the past.
4857
*
4958
* @param options The optional options object.
50-
* @param options.years The range of years the date may be in the past. Defaults to `1`.
59+
* @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`.
5160
* @param options.refDate The date to use as reference point for the newly generated date. Defaults to `faker.defaultRefDate()`.
5261
*
62+
* @throws {FakerError} If `years.max` is less than 0.
63+
* @throws {FakerError} If `years.min` is greater or equal than `years.max`.
64+
*
5365
* @see faker.date.recent(): For generating dates in the recent past (days instead of years).
5466
*
5567
* @example
5668
* faker.date.past() // '2021-12-03T05:40:44.408Z'
5769
* faker.date.past({ years: 10 }) // '2017-10-25T21:34:19.488Z'
70+
* faker.date.past({ years: { min: 4, max: 7 } }) // '2022-12-12T03:43:16.434Z'
5871
* faker.date.past({ years: 10, refDate: '2020-01-01T00:00:00.000Z' }) // '2017-08-18T02:59:12.350Z'
5972
*
6073
* @since 8.0.0
@@ -66,7 +79,22 @@ export class SimpleDateModule extends SimpleModuleBase {
6679
*
6780
* @default 1
6881
*/
69-
years?: number;
82+
years?:
83+
| number
84+
| {
85+
/**
86+
* The minimum amount of years the date should be in the past.
87+
*
88+
* @default 0
89+
*/
90+
min: number;
91+
/**
92+
* The maximum amount of years the date should be in the past.
93+
*
94+
* @default 1
95+
*/
96+
max: number;
97+
};
7098
/**
7199
* The date to use as reference point for the newly generated date.
72100
*
@@ -75,32 +103,46 @@ export class SimpleDateModule extends SimpleModuleBase {
75103
refDate?: string | Date | number;
76104
} = {}
77105
): Date {
78-
const { years = 1, refDate = this.faker.defaultRefDate() } = options;
106+
const { refDate = this.faker.defaultRefDate() } = options;
107+
let { years = 1 } = options;
108+
if (typeof years === 'number') {
109+
years = { min: 0, max: years };
110+
}
79111

80-
if (years <= 0) {
112+
if (years.max <= 0) {
81113
throw new FakerError('Years must be greater than 0.');
82114
}
83115

116+
if (years.min >= years.max) {
117+
throw new FakerError(
118+
'The maximum amount of years must be greater than the minimum amount of years.'
119+
);
120+
}
121+
84122
const time = toDate(refDate).getTime();
85123

86124
return this.between({
87-
from: time - years * 365 * 24 * 3600 * 1000,
88-
to: time - 1000,
125+
from: time - yearsToMs(years.max),
126+
to: time - yearsToMs(years.min) - 1000,
89127
});
90128
}
91129

92130
/**
93131
* Generates a random date in the future.
94132
*
95133
* @param options The optional options object.
96-
* @param options.years The range of years the date may be in the future. Defaults to `1`.
134+
* @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`.
97135
* @param options.refDate The date to use as reference point for the newly generated date. Defaults to `faker.defaultRefDate()`.
98136
*
137+
* @throws {FakerError} If `years.max` is less than 0.
138+
* @throws {FakerError} If `years.min` is greater or equal than `years.max`.
139+
*
99140
* @see faker.date.soon(): For generating dates in the near future (days instead of years).
100141
*
101142
* @example
102143
* faker.date.future() // '2022-11-19T05:52:49.100Z'
103144
* faker.date.future({ years: 10 }) // '2030-11-23T09:38:28.710Z'
145+
* faker.date.future({ years: { min: 4, max: 7 } }) // '2031-05-21T05:49:21.116Z'
104146
* faker.date.future({ years: 10, refDate: '2020-01-01T00:00:00.000Z' }) // '2020-12-13T22:45:10.252Z'
105147
*
106148
* @since 8.0.0
@@ -112,7 +154,22 @@ export class SimpleDateModule extends SimpleModuleBase {
112154
*
113155
* @default 1
114156
*/
115-
years?: number;
157+
years?:
158+
| number
159+
| {
160+
/**
161+
* The minimum amount of years the date should be in the future.
162+
*
163+
* @default 0
164+
*/
165+
min: number;
166+
/**
167+
* The maximum amount of years the date should be in the future.
168+
*
169+
* @default 1
170+
*/
171+
max: number;
172+
};
116173
/**
117174
* The date to use as reference point for the newly generated date.
118175
*
@@ -121,17 +178,27 @@ export class SimpleDateModule extends SimpleModuleBase {
121178
refDate?: string | Date | number;
122179
} = {}
123180
): Date {
124-
const { years = 1, refDate = this.faker.defaultRefDate() } = options;
181+
const { refDate = this.faker.defaultRefDate() } = options;
182+
let { years = 1 } = options;
183+
if (typeof years === 'number') {
184+
years = { min: 0, max: years };
185+
}
125186

126-
if (years <= 0) {
187+
if (years.max <= 0) {
127188
throw new FakerError('Years must be greater than 0.');
128189
}
129190

191+
if (years.min >= years.max) {
192+
throw new FakerError(
193+
'The maximum amount of years must be greater than the minimum amount of years.'
194+
);
195+
}
196+
130197
const time = toDate(refDate).getTime();
131198

132199
return this.between({
133-
from: time + 1000,
134-
to: time + years * 365 * 24 * 3600 * 1000,
200+
from: time + yearsToMs(years.min) + 1000,
201+
to: time + yearsToMs(years.max),
135202
});
136203
}
137204

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

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ exports[`date > 42 > future > with only number refDate 1`] = `2021-07-08T10:07:3
8181

8282
exports[`date > 42 > future > with only string refDate 1`] = `2021-07-08T10:07:33.524Z`;
8383

84-
exports[`date > 42 > future > with value 1`] = `2024-11-19T18:52:08.216Z`;
84+
exports[`date > 42 > future > with value numeric 1`] = `2024-11-19T18:52:08.216Z`;
85+
86+
exports[`date > 42 > future > with value range 1`] = `2027-07-06T01:53:51.028Z`;
8587

8688
exports[`date > 42 > month > noArgs 1`] = `"January"`;
8789

@@ -97,7 +99,9 @@ exports[`date > 42 > past > with only number refDate 1`] = `2020-07-08T10:07:32.
9799

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

100-
exports[`date > 42 > past > with value 1`] = `2014-11-22T18:52:07.216Z`;
102+
exports[`date > 42 > past > with value numeric 1`] = `2014-11-22T18:52:07.216Z`;
103+
104+
exports[`date > 42 > past > with value range 1`] = `2012-07-09T01:53:50.028Z`;
101105

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

@@ -207,7 +211,9 @@ exports[`date > 1211 > future > with only number refDate 1`] = `2022-01-26T14:59
207211

208212
exports[`date > 1211 > future > with only string refDate 1`] = `2022-01-26T14:59:27.356Z`;
209213

210-
exports[`date > 1211 > future > with value 1`] = `2030-06-03T19:31:11.518Z`;
214+
exports[`date > 1211 > future > with value numeric 1`] = `2030-06-03T19:31:11.518Z`;
215+
216+
exports[`date > 1211 > future > with value range 1`] = `2032-06-28T21:40:59.944Z`;
211217

212218
exports[`date > 1211 > month > noArgs 1`] = `"September"`;
213219

@@ -223,7 +229,9 @@ exports[`date > 1211 > past > with only number refDate 1`] = `2021-01-26T14:59:2
223229

224230
exports[`date > 1211 > past > with only string refDate 1`] = `2021-01-26T14:59:26.356Z`;
225231

226-
exports[`date > 1211 > past > with value 1`] = `2020-06-05T19:31:10.518Z`;
232+
exports[`date > 1211 > past > with value numeric 1`] = `2020-06-05T19:31:10.518Z`;
233+
234+
exports[`date > 1211 > past > with value range 1`] = `2017-07-02T21:40:58.944Z`;
227235

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

@@ -331,7 +339,9 @@ exports[`date > 1337 > future > with only number refDate 1`] = `2021-05-28T08:29
331339

332340
exports[`date > 1337 > future > with only string refDate 1`] = `2021-05-28T08:29:26.600Z`;
333341

334-
exports[`date > 1337 > future > with value 1`] = `2023-10-06T02:30:57.962Z`;
342+
exports[`date > 1337 > future > with value numeric 1`] = `2023-10-06T02:30:57.962Z`;
343+
344+
exports[`date > 1337 > future > with value range 1`] = `2026-07-01T11:10:47.810Z`;
335345

336346
exports[`date > 1337 > month > noArgs 1`] = `"February"`;
337347

@@ -347,7 +357,9 @@ exports[`date > 1337 > past > with only number refDate 1`] = `2020-05-28T08:29:2
347357

348358
exports[`date > 1337 > past > with only string refDate 1`] = `2020-05-28T08:29:25.600Z`;
349359

350-
exports[`date > 1337 > past > with value 1`] = `2013-10-08T02:30:56.962Z`;
360+
exports[`date > 1337 > past > with value numeric 1`] = `2013-10-08T02:30:56.962Z`;
361+
362+
exports[`date > 1337 > past > with value range 1`] = `2011-07-05T11:10:46.810Z`;
351363

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

test/modules/date.spec.ts

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ describe('date', () => {
4444
.it('with only number refDate', {
4545
refDate: new Date(refDate).getTime(),
4646
})
47-
.it('with value', { years: 10, refDate });
47+
.it('with value numeric', { years: 10, refDate })
48+
.it('with value range', { years: { min: 3, max: 12 }, refDate });
4849
});
4950

5051
t.describeEach(
@@ -190,13 +191,60 @@ describe('date', () => {
190191
expect(date).greaterThanOrEqual(yearsAgo);
191192
});
192193

194+
it('should return a date between 20 and 40 years in the past', () => {
195+
const today = new Date();
196+
const yearsAgoMax = 40;
197+
const yearAgoMax = new Date(today);
198+
yearAgoMax.setFullYear(yearAgoMax.getFullYear() - yearsAgoMax);
199+
200+
const yearsAgoMin = 20;
201+
const yearAgoMin = new Date(today);
202+
yearAgoMin.setFullYear(yearAgoMin.getFullYear() - yearsAgoMin);
203+
204+
const date = faker.date.past({
205+
years: { min: yearsAgoMin, max: yearsAgoMax },
206+
});
207+
208+
expect(date).lessThan(today);
209+
expect(date).lessThan(yearAgoMin);
210+
expect(date).greaterThanOrEqual(yearAgoMax);
211+
});
212+
193213
it('should throw an error when years = 0', () => {
194214
const refDate = new Date();
195215
expect(() =>
196216
faker.date.past({ years: 0, refDate: refDate.toISOString() })
197217
).toThrow(new FakerError('Years must be greater than 0.'));
198218
});
199219

220+
it('should throw an error when years.min > years.max', () => {
221+
const refDate = new Date();
222+
expect(() =>
223+
faker.date.past({
224+
years: { min: 3, max: 2 },
225+
refDate: refDate.toISOString(),
226+
})
227+
).toThrow(
228+
new FakerError(
229+
'The maximum amount of years must be greater than the minimum amount of years.'
230+
)
231+
);
232+
});
233+
234+
it('should throw an error when years.min = years.max', () => {
235+
const refDate = new Date();
236+
expect(() =>
237+
faker.date.past({
238+
years: { min: 6, max: 6 },
239+
refDate: refDate.toISOString(),
240+
})
241+
).toThrow(
242+
new FakerError(
243+
'The maximum amount of years must be greater than the minimum amount of years.'
244+
)
245+
);
246+
});
247+
200248
it.each(converterMap)(
201249
'should return a past date relative to given refDate',
202250
(converter) => {
@@ -216,9 +264,34 @@ describe('date', () => {
216264

217265
describe('future()', () => {
218266
it('should return a date 75 years into the future', () => {
219-
const date = faker.date.future({ years: 75 });
267+
const today = new Date();
268+
const yearsUntilMax = 75;
269+
const yearUntilMax = new Date(today);
270+
yearUntilMax.setFullYear(yearUntilMax.getFullYear() + yearsUntilMax);
220271

221-
expect(date).greaterThan(new Date());
272+
const date = faker.date.future({ years: yearsUntilMax });
273+
274+
expect(date).greaterThan(today);
275+
expect(date).lessThanOrEqual(yearUntilMax);
276+
});
277+
278+
it('should return a date between 20 and 40 years in the future', () => {
279+
const today = new Date();
280+
const yearsUntilMin = 20;
281+
const yearUntilMin = new Date(today);
282+
yearUntilMin.setFullYear(yearUntilMin.getFullYear() + yearsUntilMin);
283+
284+
const yearsUntilMax = 40;
285+
const yearUntilMax = new Date(today);
286+
yearUntilMax.setFullYear(yearUntilMax.getFullYear() + yearsUntilMax);
287+
288+
const date = faker.date.future({
289+
years: { min: yearsUntilMin, max: yearsUntilMax },
290+
});
291+
292+
expect(date).greaterThan(today);
293+
expect(date).greaterThan(yearUntilMin);
294+
expect(date).lessThanOrEqual(yearUntilMax);
222295
});
223296

224297
it('should throw an error when years = 0', () => {
@@ -228,6 +301,34 @@ describe('date', () => {
228301
).toThrow(new FakerError('Years must be greater than 0.'));
229302
});
230303

304+
it('should throw an error when years.min > years.max', () => {
305+
const refDate = new Date();
306+
expect(() =>
307+
faker.date.future({
308+
years: { min: 3, max: 2 },
309+
refDate: refDate.toISOString(),
310+
})
311+
).toThrow(
312+
new FakerError(
313+
'The maximum amount of years must be greater than the minimum amount of years.'
314+
)
315+
);
316+
});
317+
318+
it('should throw an error when years.min = years.max', () => {
319+
const refDate = new Date();
320+
expect(() =>
321+
faker.date.future({
322+
years: { min: 6, max: 6 },
323+
refDate: refDate.toISOString(),
324+
})
325+
).toThrow(
326+
new FakerError(
327+
'The maximum amount of years must be greater than the minimum amount of years.'
328+
)
329+
);
330+
});
331+
231332
it.each(converterMap)(
232333
'should return a date 75 years after the date given',
233334
(converter) => {

0 commit comments

Comments
 (0)