1 | import { addDays } from "date-fns/addDays";
|
2 | import { addSeconds } from "date-fns/addSeconds";
|
3 | import { addMinutes } from "date-fns/addMinutes";
|
4 | import { addHours } from "date-fns/addHours";
|
5 | import { addWeeks } from "date-fns/addWeeks";
|
6 | import { addMonths } from "date-fns/addMonths";
|
7 | import { addYears } from "date-fns/addYears";
|
8 | import { differenceInYears } from "date-fns/differenceInYears";
|
9 | import { differenceInQuarters } from "date-fns/differenceInQuarters";
|
10 | import { differenceInMonths } from "date-fns/differenceInMonths";
|
11 | import { differenceInWeeks } from "date-fns/differenceInWeeks";
|
12 | import { differenceInDays } from "date-fns/differenceInDays";
|
13 | import { differenceInHours } from "date-fns/differenceInHours";
|
14 | import { differenceInMinutes } from "date-fns/differenceInMinutes";
|
15 | import { differenceInSeconds } from "date-fns/differenceInSeconds";
|
16 | import { differenceInMilliseconds } from "date-fns/differenceInMilliseconds";
|
17 | import { eachDayOfInterval } from "date-fns/eachDayOfInterval";
|
18 | import { endOfDay } from "date-fns/endOfDay";
|
19 | import { endOfWeek } from "date-fns/endOfWeek";
|
20 | import { endOfYear } from "date-fns/endOfYear";
|
21 | import { format, longFormatters } from "date-fns/format";
|
22 | import { getDate } from "date-fns/getDate";
|
23 | import { getDay } from "date-fns/getDay";
|
24 | import { getDaysInMonth } from "date-fns/getDaysInMonth";
|
25 | import { getHours } from "date-fns/getHours";
|
26 | import { getMinutes } from "date-fns/getMinutes";
|
27 | import { getMonth } from "date-fns/getMonth";
|
28 | import { getSeconds } from "date-fns/getSeconds";
|
29 | import { getYear } from "date-fns/getYear";
|
30 | import { isAfter } from "date-fns/isAfter";
|
31 | import { isBefore } from "date-fns/isBefore";
|
32 | import { isEqual } from "date-fns/isEqual";
|
33 | import { isSameDay } from "date-fns/isSameDay";
|
34 | import { isSameYear } from "date-fns/isSameYear";
|
35 | import { isSameMonth } from "date-fns/isSameMonth";
|
36 | import { isSameHour } from "date-fns/isSameHour";
|
37 | import { isValid } from "date-fns/isValid";
|
38 | import { parse as dateFnsParse } from "date-fns/parse";
|
39 | import { setDate } from "date-fns/setDate";
|
40 | import { setHours } from "date-fns/setHours";
|
41 | import { setMinutes } from "date-fns/setMinutes";
|
42 | import { setMonth } from "date-fns/setMonth";
|
43 | import { setSeconds } from "date-fns/setSeconds";
|
44 | import { setYear } from "date-fns/setYear";
|
45 | import { startOfDay } from "date-fns/startOfDay";
|
46 | import { startOfMonth } from "date-fns/startOfMonth";
|
47 | import { endOfMonth } from "date-fns/endOfMonth";
|
48 | import { startOfWeek } from "date-fns/startOfWeek";
|
49 | import { startOfYear } from "date-fns/startOfYear";
|
50 | import { parseISO } from "date-fns/parseISO";
|
51 | import { formatISO } from "date-fns/formatISO";
|
52 | import { IUtils, DateIOFormats, Unit } from "@date-io/core/IUtils";
|
53 | import { isWithinInterval } from "date-fns/isWithinInterval";
|
54 | import { enUS as defaultLocale } from "date-fns/locale/en-US";
|
55 |
|
56 | type Locale = typeof defaultLocale;
|
57 |
|
58 | const defaultFormats: DateIOFormats = {
|
59 | dayOfMonth: "d",
|
60 | fullDate: "PP",
|
61 | fullDateWithWeekday: "PPPP",
|
62 | fullDateTime: "PP p",
|
63 | fullDateTime12h: "PP hh:mm aa",
|
64 | fullDateTime24h: "PP HH:mm",
|
65 | fullTime: "p",
|
66 | fullTime12h: "hh:mm aa",
|
67 | fullTime24h: "HH:mm",
|
68 | hours12h: "hh",
|
69 | hours24h: "HH",
|
70 | keyboardDate: "P",
|
71 | keyboardDateTime: "P p",
|
72 | keyboardDateTime12h: "P hh:mm aa",
|
73 | keyboardDateTime24h: "P HH:mm",
|
74 | minutes: "mm",
|
75 | month: "LLLL",
|
76 | monthAndDate: "MMMM d",
|
77 | monthAndYear: "LLLL yyyy",
|
78 | monthShort: "MMM",
|
79 | weekday: "EEEE",
|
80 | weekdayShort: "EEE",
|
81 | normalDate: "d MMMM",
|
82 | normalDateWithWeekday: "EEE, MMM d",
|
83 | seconds: "ss",
|
84 | shortDate: "MMM d",
|
85 | year: "yyyy",
|
86 | };
|
87 |
|
88 | export default class DateFnsUtils implements IUtils<Date, Locale> {
|
89 | public lib = "date-fns";
|
90 | public locale?: Locale;
|
91 | public formats: DateIOFormats;
|
92 |
|
93 | constructor({
|
94 | locale,
|
95 | formats,
|
96 | }: { formats?: Partial<DateIOFormats>; locale?: Locale; instance?: any } = {}) {
|
97 | this.locale = locale;
|
98 | this.formats = Object.assign({}, defaultFormats, formats);
|
99 | }
|
100 |
|
101 |
|
102 |
|
103 | public is12HourCycleInCurrentLocale = () => {
|
104 | if (this.locale) {
|
105 | return /a/.test(this.locale.formatLong?.time({}));
|
106 | }
|
107 |
|
108 |
|
109 | return true;
|
110 | };
|
111 |
|
112 | public getFormatHelperText = (format: string) => {
|
113 |
|
114 | const longFormatRegexp = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g;
|
115 | const locale = this.locale || defaultLocale;
|
116 |
|
117 | return (
|
118 | format
|
119 | .match(longFormatRegexp)
|
120 | ?.map((token) => {
|
121 | const firstCharacter = token[0];
|
122 | if (firstCharacter === "p" || firstCharacter === "P") {
|
123 | const longFormatter = longFormatters[firstCharacter];
|
124 | return longFormatter(token, locale.formatLong);
|
125 | }
|
126 | return token;
|
127 | })
|
128 | .join("")
|
129 | .replace(/(aaa|aa|a)/g, "(a|p)m")
|
130 | .toLocaleLowerCase() ?? format
|
131 | );
|
132 | };
|
133 |
|
134 | public parseISO = (isoString: string) => {
|
135 | return parseISO(isoString);
|
136 | };
|
137 |
|
138 | public toISO = (value: Date) => {
|
139 | return formatISO(value, { format: "extended" });
|
140 | };
|
141 |
|
142 | public getCurrentLocaleCode = () => {
|
143 | return this.locale?.code || "en-US";
|
144 | };
|
145 |
|
146 | public addSeconds = (value: Date, count: number) => {
|
147 | return addSeconds(value, count);
|
148 | };
|
149 |
|
150 | public addMinutes = (value: Date, count: number) => {
|
151 | return addMinutes(value, count);
|
152 | };
|
153 |
|
154 | public addHours = (value: Date, count: number) => {
|
155 | return addHours(value, count);
|
156 | };
|
157 |
|
158 | public addDays = (value: Date, count: number) => {
|
159 | return addDays(value, count);
|
160 | };
|
161 |
|
162 | public addWeeks = (value: Date, count: number) => {
|
163 | return addWeeks(value, count);
|
164 | };
|
165 |
|
166 | public addMonths = (value: Date, count: number) => {
|
167 | return addMonths(value, count);
|
168 | };
|
169 |
|
170 | public addYears = (value: Date, count: number) => {
|
171 | return addYears(value, count);
|
172 | };
|
173 |
|
174 | public isValid = (value: any) => {
|
175 | return isValid(this.date(value));
|
176 | };
|
177 |
|
178 | public getDiff = (value: Date, comparing: Date | string, unit?: Unit) => {
|
179 |
|
180 | const dateToCompare = this.date(comparing) ?? value;
|
181 | if (!this.isValid(dateToCompare)) {
|
182 | return 0;
|
183 | }
|
184 | switch (unit) {
|
185 | case "years":
|
186 | return differenceInYears(value, dateToCompare);
|
187 | case "quarters":
|
188 | return differenceInQuarters(value, dateToCompare);
|
189 | case "months":
|
190 | return differenceInMonths(value, dateToCompare);
|
191 | case "weeks":
|
192 | return differenceInWeeks(value, dateToCompare);
|
193 | case "days":
|
194 | return differenceInDays(value, dateToCompare);
|
195 | case "hours":
|
196 | return differenceInHours(value, dateToCompare);
|
197 | case "minutes":
|
198 | return differenceInMinutes(value, dateToCompare);
|
199 | case "seconds":
|
200 | return differenceInSeconds(value, dateToCompare);
|
201 | default: {
|
202 | return differenceInMilliseconds(value, dateToCompare);
|
203 | }
|
204 | }
|
205 | };
|
206 |
|
207 | public isAfter = (value: Date, comparing: Date) => {
|
208 | return isAfter(value, comparing);
|
209 | };
|
210 |
|
211 | public isBefore = (value: Date, comparing: Date) => {
|
212 | return isBefore(value, comparing);
|
213 | };
|
214 |
|
215 | public startOfDay = (value: Date) => {
|
216 | return startOfDay(value);
|
217 | };
|
218 |
|
219 | public endOfDay = (value: Date) => {
|
220 | return endOfDay(value);
|
221 | };
|
222 |
|
223 | public getHours = (value: Date) => {
|
224 | return getHours(value);
|
225 | };
|
226 |
|
227 | public setHours = (value: Date, count: number) => {
|
228 | return setHours(value, count);
|
229 | };
|
230 |
|
231 | public setMinutes = (value: Date, count: number) => {
|
232 | return setMinutes(value, count);
|
233 | };
|
234 |
|
235 | public getSeconds = (value: Date) => {
|
236 | return getSeconds(value);
|
237 | };
|
238 |
|
239 | public setSeconds = (value: Date, count: number) => {
|
240 | return setSeconds(value, count);
|
241 | };
|
242 |
|
243 | public isSameDay = (value: Date, comparing: Date) => {
|
244 | return isSameDay(value, comparing);
|
245 | };
|
246 |
|
247 | public isSameMonth = (value: Date, comparing: Date) => {
|
248 | return isSameMonth(value, comparing);
|
249 | };
|
250 |
|
251 | public isSameYear = (value: Date, comparing: Date) => {
|
252 | return isSameYear(value, comparing);
|
253 | };
|
254 |
|
255 | public isSameHour = (value: Date, comparing: Date) => {
|
256 | return isSameHour(value, comparing);
|
257 | };
|
258 |
|
259 | public startOfYear = (value: Date) => {
|
260 | return startOfYear(value);
|
261 | };
|
262 |
|
263 | public endOfYear = (value: Date) => {
|
264 | return endOfYear(value);
|
265 | };
|
266 |
|
267 | public startOfMonth = (value: Date) => {
|
268 | return startOfMonth(value);
|
269 | };
|
270 |
|
271 | public endOfMonth = (value: Date) => {
|
272 | return endOfMonth(value);
|
273 | };
|
274 |
|
275 | public startOfWeek = (value: Date) => {
|
276 | return startOfWeek(value, { locale: this.locale });
|
277 | };
|
278 |
|
279 | public endOfWeek = (value: Date) => {
|
280 | return endOfWeek(value, { locale: this.locale });
|
281 | };
|
282 |
|
283 | public getYear = (value: Date) => {
|
284 | return getYear(value);
|
285 | };
|
286 |
|
287 | public setYear = (value: Date, count: number) => {
|
288 | return setYear(value, count);
|
289 | };
|
290 |
|
291 | date<
|
292 | TArg extends unknown = undefined,
|
293 | TRes extends unknown = TArg extends null
|
294 | ? null
|
295 | : TArg extends undefined
|
296 | ? Date
|
297 | : Date | null
|
298 | >(value?: TArg): TRes {
|
299 | if (typeof value === "undefined") {
|
300 | return new Date() as TRes;
|
301 | }
|
302 |
|
303 | if (value === null) {
|
304 | return null as TRes;
|
305 | }
|
306 |
|
307 | return new Date(value as string | number) as TRes;
|
308 | }
|
309 |
|
310 | public toJsDate = (value: Date) => {
|
311 | return value;
|
312 | };
|
313 |
|
314 | public parse = (value: string, formatString: string) => {
|
315 | if (value === "") {
|
316 | return null;
|
317 | }
|
318 |
|
319 | return dateFnsParse(value, formatString, new Date(), { locale: this.locale });
|
320 | };
|
321 |
|
322 | public format = (date: Date, formatKey: keyof DateIOFormats) => {
|
323 | return this.formatByString(date, this.formats[formatKey]);
|
324 | };
|
325 |
|
326 | public formatByString = (date: Date, formatString: string) => {
|
327 | return format(date, formatString, { locale: this.locale });
|
328 | };
|
329 |
|
330 | public isEqual = (date: any, comparing: any) => {
|
331 | if (date === null && comparing === null) {
|
332 | return true;
|
333 | }
|
334 |
|
335 | return isEqual(date, comparing);
|
336 | };
|
337 |
|
338 | public isNull = (date: Date) => {
|
339 | return date === null;
|
340 | };
|
341 |
|
342 | public isAfterDay = (date: Date, value: Date) => {
|
343 | return isAfter(date, endOfDay(value));
|
344 | };
|
345 |
|
346 | public isBeforeDay = (date: Date, value: Date) => {
|
347 | return isBefore(date, startOfDay(value));
|
348 | };
|
349 |
|
350 | public isBeforeYear = (date: Date, value: Date) => {
|
351 | return isBefore(date, startOfYear(value));
|
352 | };
|
353 |
|
354 | public isBeforeMonth(value: Date, comparing: Date): boolean {
|
355 | return isBefore(value, startOfMonth(comparing));
|
356 | }
|
357 |
|
358 | public isAfterMonth(value: Date, comparing: Date): boolean {
|
359 | return isAfter(value, startOfMonth(comparing));
|
360 | }
|
361 |
|
362 | public isAfterYear = (date: Date, value: Date) => {
|
363 | return isAfter(date, endOfYear(value));
|
364 | };
|
365 |
|
366 | public isWithinRange = (date: Date, [start, end]: [Date, Date]) => {
|
367 | return isWithinInterval(date, { start, end });
|
368 | };
|
369 |
|
370 | public formatNumber = (numberToFormat: string) => {
|
371 | return numberToFormat;
|
372 | };
|
373 |
|
374 | public getMinutes = (date: Date) => {
|
375 | return getMinutes(date);
|
376 | };
|
377 |
|
378 | public getDate = (date: Date) => {
|
379 | return getDate(date);
|
380 | };
|
381 |
|
382 | public setDate = (date: Date, count: number) => {
|
383 | return setDate(date, count);
|
384 | };
|
385 |
|
386 | public getMonth = (date: Date) => {
|
387 | return getMonth(date);
|
388 | };
|
389 |
|
390 | public getDaysInMonth = (date: Date) => {
|
391 | return getDaysInMonth(date);
|
392 | };
|
393 |
|
394 | public setMonth = (date: Date, count: number) => {
|
395 | return setMonth(date, count);
|
396 | };
|
397 |
|
398 | public getMeridiemText = (ampm: "am" | "pm") => {
|
399 | return ampm === "am" ? "AM" : "PM";
|
400 | };
|
401 |
|
402 | public getNextMonth = (date: Date) => {
|
403 | return addMonths(date, 1);
|
404 | };
|
405 |
|
406 | public getPreviousMonth = (date: Date) => {
|
407 | return addMonths(date, -1);
|
408 | };
|
409 |
|
410 | public getMonthArray = (date: Date) => {
|
411 | const firstMonth = startOfYear(date);
|
412 | const monthArray = [firstMonth];
|
413 |
|
414 | while (monthArray.length < 12) {
|
415 | const prevMonth = monthArray[monthArray.length - 1];
|
416 | monthArray.push(this.getNextMonth(prevMonth));
|
417 | }
|
418 |
|
419 | return monthArray;
|
420 | };
|
421 |
|
422 | public mergeDateAndTime = (date: Date, time: Date) => {
|
423 | return this.setSeconds(
|
424 | this.setMinutes(this.setHours(date, this.getHours(time)), this.getMinutes(time)),
|
425 | this.getSeconds(time)
|
426 | );
|
427 | };
|
428 |
|
429 | public getWeekdays = () => {
|
430 | const now = new Date();
|
431 | return eachDayOfInterval({
|
432 | start: startOfWeek(now, { locale: this.locale }),
|
433 | end: endOfWeek(now, { locale: this.locale }),
|
434 | }).map((day) => this.formatByString(day, "EEEEEE"));
|
435 | };
|
436 |
|
437 | public getWeekArray = (date: Date) => {
|
438 | const start = startOfWeek(startOfMonth(date), { locale: this.locale });
|
439 | const end = endOfWeek(endOfMonth(date), { locale: this.locale });
|
440 |
|
441 | let count = 0;
|
442 | let current = start;
|
443 | const nestedWeeks: Date[][] = [];
|
444 | let lastDay = null as null | number;
|
445 | while (isBefore(current, end)) {
|
446 | const weekNumber = Math.floor(count / 7);
|
447 | nestedWeeks[weekNumber] = nestedWeeks[weekNumber] || [];
|
448 | const day = getDay(current);
|
449 | if (lastDay !== day) {
|
450 | lastDay = day;
|
451 | nestedWeeks[weekNumber].push(current);
|
452 | count += 1;
|
453 | }
|
454 | current = addDays(current, 1);
|
455 | }
|
456 | return nestedWeeks;
|
457 | };
|
458 |
|
459 | public getYearRange = (start: Date, end: Date) => {
|
460 | const startDate = startOfYear(start);
|
461 | const endDate = endOfYear(end);
|
462 | const years: Date[] = [];
|
463 |
|
464 | let current = startDate;
|
465 | while (isBefore(current, endDate)) {
|
466 | years.push(current);
|
467 | current = addYears(current, 1);
|
468 | }
|
469 |
|
470 | return years;
|
471 | };
|
472 | }
|