UNPKG

13.6 kBPlain TextView Raw
1import { addDays } from "date-fns/addDays";
2import { addSeconds } from "date-fns/addSeconds";
3import { addMinutes } from "date-fns/addMinutes";
4import { addHours } from "date-fns/addHours";
5import { addWeeks } from "date-fns/addWeeks";
6import { addMonths } from "date-fns/addMonths";
7import { addYears } from "date-fns/addYears";
8import { differenceInYears } from "date-fns/differenceInYears";
9import { differenceInQuarters } from "date-fns/differenceInQuarters";
10import { differenceInMonths } from "date-fns/differenceInMonths";
11import { differenceInWeeks } from "date-fns/differenceInWeeks";
12import { differenceInDays } from "date-fns/differenceInDays";
13import { differenceInHours } from "date-fns/differenceInHours";
14import { differenceInMinutes } from "date-fns/differenceInMinutes";
15import { differenceInSeconds } from "date-fns/differenceInSeconds";
16import { differenceInMilliseconds } from "date-fns/differenceInMilliseconds";
17import { eachDayOfInterval } from "date-fns/eachDayOfInterval";
18import { endOfDay } from "date-fns/endOfDay";
19import { endOfWeek } from "date-fns/endOfWeek";
20import { endOfYear } from "date-fns/endOfYear";
21import { format, longFormatters } from "date-fns/format";
22import { getDate } from "date-fns/getDate";
23import { getDay } from "date-fns/getDay";
24import { getDaysInMonth } from "date-fns/getDaysInMonth";
25import { getHours } from "date-fns/getHours";
26import { getMinutes } from "date-fns/getMinutes";
27import { getMonth } from "date-fns/getMonth";
28import { getSeconds } from "date-fns/getSeconds";
29import { getYear } from "date-fns/getYear";
30import { isAfter } from "date-fns/isAfter";
31import { isBefore } from "date-fns/isBefore";
32import { isEqual } from "date-fns/isEqual";
33import { isSameDay } from "date-fns/isSameDay";
34import { isSameYear } from "date-fns/isSameYear";
35import { isSameMonth } from "date-fns/isSameMonth";
36import { isSameHour } from "date-fns/isSameHour";
37import { isValid } from "date-fns/isValid";
38import { parse as dateFnsParse } from "date-fns/parse";
39import { setDate } from "date-fns/setDate";
40import { setHours } from "date-fns/setHours";
41import { setMinutes } from "date-fns/setMinutes";
42import { setMonth } from "date-fns/setMonth";
43import { setSeconds } from "date-fns/setSeconds";
44import { setYear } from "date-fns/setYear";
45import { startOfDay } from "date-fns/startOfDay";
46import { startOfMonth } from "date-fns/startOfMonth";
47import { endOfMonth } from "date-fns/endOfMonth";
48import { startOfWeek } from "date-fns/startOfWeek";
49import { startOfYear } from "date-fns/startOfYear";
50import { parseISO } from "date-fns/parseISO";
51import { formatISO } from "date-fns/formatISO";
52import { IUtils, DateIOFormats, Unit } from "@date-io/core/IUtils";
53import { isWithinInterval } from "date-fns/isWithinInterval";
54import { enUS as defaultLocale } from "date-fns/locale/en-US";
55
56type Locale = typeof defaultLocale;
57
58const 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
88export 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 // Note: date-fns input types are more lenient than this adapter, so we need to expose our more
102 // strict signature and delegate to the more lenient signature. Otherwise, we have downstream type errors upon usage.
103 public is12HourCycleInCurrentLocale = () => {
104 if (this.locale) {
105 return /a/.test(this.locale.formatLong?.time({}));
106 }
107
108 // By default date-fns is using en-US locale with am/pm enabled
109 return true;
110 };
111
112 public getFormatHelperText = (format: string) => {
113 // @see https://github.com/date-fns/date-fns/blob/master/src/format/index.js#L31
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 // we output 0 if the compare date is string and parsing is not valid
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}