mirror of
https://github.com/Litlyx/litlyx
synced 2025-12-09 23:48:36 +01:00
160 lines
5.5 KiB
TypeScript
160 lines
5.5 KiB
TypeScript
|
|
import dayjs from 'dayjs';
|
|
import * as fns from 'date-fns';
|
|
|
|
|
|
const slices = ['hour', 'day', 'week', 'month', 'year'] as const;
|
|
|
|
export type Slice = typeof slices[number];
|
|
|
|
export function isValidSlice(slice: string): asserts slice is Slice {
|
|
if (!slices.includes(slice as any)) throw Error('Slice not valid');
|
|
}
|
|
|
|
const startOfFunctions: { [key in Slice]: (date: Date) => Date } = {
|
|
hour: fns.startOfHour,
|
|
day: fns.startOfDay,
|
|
week: fns.startOfWeek,
|
|
month: fns.startOfMonth,
|
|
year: fns.startOfYear
|
|
};
|
|
|
|
const endOfFunctions: { [key in Slice]: (date: Date) => Date } = {
|
|
hour: fns.endOfHour,
|
|
day: fns.endOfDay,
|
|
week: fns.endOfWeek,
|
|
month: fns.endOfMonth,
|
|
year: fns.endOfYear
|
|
};
|
|
|
|
class DateService {
|
|
|
|
getChartLabelFromISO(timestamp: number, slice: Slice) {
|
|
const date = new Date(timestamp);
|
|
if (slice === 'hour') return fns.format(date, 'HH:mm');
|
|
if (slice === 'day') return fns.format(date, 'dd/MM');
|
|
if (slice === 'week') return fns.format(date, 'dd/MM');
|
|
if (slice === 'month') return fns.format(date, 'MMMM');
|
|
if (slice === 'year') return fns.format(date, 'YYYY');
|
|
return date.toISOString();
|
|
}
|
|
|
|
public sliceAvailabilityMap: Record<Slice, [number, number]> = {
|
|
hour: [0, 3],
|
|
day: [2, 31 * 2],
|
|
week: [0, 0],
|
|
month: [31 * 2, 365 * 4],
|
|
year: [365, 365 * 20]
|
|
}
|
|
|
|
canUseSlice(from: string | number | Date, to: string | number | Date, slice: Slice) {
|
|
|
|
const daysDiff = fns.differenceInDays(
|
|
new Date(new Date(to).getTime() + 1000),
|
|
new Date(from)
|
|
);
|
|
|
|
return this.canUseSliceFromDays(daysDiff, slice);
|
|
}
|
|
|
|
canUseSliceFromDays(days: number, slice: Slice): [false, string] | [true, number] {
|
|
// HOUR - 3 DAYS - 72 SAMPLES
|
|
if (slice === 'hour' && (days > 3)) return [false, 'Date gap too big for this slice'];
|
|
// DAY - 2 MONTHS - 62 SAMPLES
|
|
if (slice === 'day' && (days > 31 * 2)) return [false, 'Date gap too big for this slice'];
|
|
// MONTH - 4 YEARS - 60 SAMPLES
|
|
if (slice === 'month' && (days > 365 * 4)) return [false, 'Date gap too big for this slice'];
|
|
|
|
// DAY - 2 DAYS - 2 SAMPLES
|
|
if (slice === 'day' && (days < 2)) return [false, 'Date gap too small for this slice'];
|
|
// MONTH - 2 MONTHS - 2 SAMPLES
|
|
if (slice === 'month' && (days < 31 * 2)) return [false, 'Date gap too small for this slice'];
|
|
|
|
return [true, days]
|
|
}
|
|
|
|
startOfSlice(date: Date, slice: Slice) {
|
|
const fn = startOfFunctions[slice];
|
|
if (!fn) throw Error(`startOfFunction of slice ${slice} not found`);
|
|
return fn(date);
|
|
}
|
|
|
|
endOfSlice(date: Date, slice: Slice) {
|
|
const fn = endOfFunctions[slice];
|
|
if (!fn) throw Error(`endOfFunction of slice ${slice} not found`);
|
|
return fn(date);
|
|
}
|
|
|
|
getGranularityData(slice: Slice, dateField: string) {
|
|
|
|
const dateFromParts: Record<string, any> = {};
|
|
let granularity;
|
|
|
|
switch (slice) {
|
|
case 'hour':
|
|
dateFromParts.hour = { $hour: { date: dateField } }
|
|
granularity = granularity || 'hour';
|
|
case 'day':
|
|
dateFromParts.day = { $dayOfMonth: { date: dateField } }
|
|
granularity = granularity || 'day';
|
|
case 'month':
|
|
dateFromParts.month = { $month: { date: dateField } }
|
|
granularity = granularity || 'month';
|
|
case 'year':
|
|
dateFromParts.year = { $year: { date: dateField } }
|
|
granularity = granularity || 'year';
|
|
}
|
|
|
|
return { dateFromParts, granularity }
|
|
}
|
|
|
|
generateDateSlices(slice: Slice, fromDate: Date, toDate: Date) {
|
|
const slices: Date[] = [];
|
|
let currentDate = fromDate;
|
|
const addFunctions: { [key in Slice]: any } = { hour: fns.addHours, day: fns.addDays, week: fns.addWeeks, month: fns.addMonths, year: fns.addYears };
|
|
const addFunction = addFunctions[slice];
|
|
if (!addFunction) { throw new Error(`Invalid slice: ${slice}`); }
|
|
while (fns.isBefore(currentDate, toDate) || currentDate.getTime() === toDate.getTime()) {
|
|
slices.push(currentDate);
|
|
currentDate = addFunction(currentDate, 1);
|
|
}
|
|
return slices;
|
|
}
|
|
|
|
isSameDayUTC(a: Date, b: Date) {
|
|
return a.getUTCFullYear() === b.getUTCFullYear() && a.getUTCMonth() === b.getUTCMonth() && a.getUTCDate() === b.getUTCDate();
|
|
}
|
|
|
|
mergeDates(timeline: { _id: string, count: number }[], allDates: Date[], slice: Slice) {
|
|
|
|
const result: { _id: string, count: number }[] = [];
|
|
|
|
const isSames: { [key in Slice]: any } = { hour: fns.isSameHour, day: this.isSameDayUTC, week: fns.isSameWeek, month: fns.isSameMonth, year: fns.isSameYear, }
|
|
|
|
const isSame = isSames[slice];
|
|
|
|
if (!isSame) {
|
|
throw new Error(`Invalid slice: ${slice}`);
|
|
}
|
|
|
|
for (const date of allDates) {
|
|
result.push({ _id: date.toISOString(), count: 0 });
|
|
for (const element of timeline) {
|
|
const elementDate = new Date(element._id);
|
|
if (isSame(elementDate, date)) {
|
|
const existingEntry = result.find(item => isSame(date, new Date(item._id)));
|
|
if (!existingEntry) throw new Error('THIS CANNOT HAPPEN');
|
|
existingEntry.count += element.count;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
const dateServiceInstance = new DateService();
|
|
export default dateServiceInstance;
|