158 lines
4.1 KiB
TypeScript
158 lines
4.1 KiB
TypeScript
import { format, formatDistanceToNow } from 'date-fns';
|
|
|
|
/**
|
|
* Calculate the average time of day from a list of timestamps
|
|
*/
|
|
export function calculateAverageTimeOfDay(timestamps: Date[]) {
|
|
if (!timestamps.length) {
|
|
return {
|
|
hour: 0,
|
|
minute: 0,
|
|
formattedTime: '00:00',
|
|
description: 'No data',
|
|
timeOfDay: 'unknown',
|
|
earliest: new Date(),
|
|
latest: new Date(),
|
|
mostFrequentHour: 0,
|
|
};
|
|
}
|
|
|
|
// Extract hours and minutes, convert to minutes since midnight
|
|
let totalMinutes = 0;
|
|
const minutesArray: number[] = [];
|
|
const hours: number[] = new Array(24).fill(0); // For hour frequency
|
|
|
|
let earliest = new Date(timestamps[0]);
|
|
let latest = new Date(timestamps[0]);
|
|
|
|
timestamps.forEach((timestamp) => {
|
|
const date = new Date(timestamp);
|
|
|
|
// Update earliest and latest
|
|
if (date < earliest) earliest = new Date(date);
|
|
if (date > latest) latest = new Date(date);
|
|
|
|
const hour = date.getHours();
|
|
const minute = date.getMinutes();
|
|
|
|
// Track hour frequency
|
|
hours[hour]++;
|
|
|
|
// Convert to minutes since midnight
|
|
const minutesSinceMidnight = hour * 60 + minute;
|
|
minutesArray.push(minutesSinceMidnight);
|
|
totalMinutes += minutesSinceMidnight;
|
|
});
|
|
|
|
// Find most frequent hour
|
|
let mostFrequentHour = 0;
|
|
let maxFrequency = 0;
|
|
hours.forEach((freq, hour) => {
|
|
if (freq > maxFrequency) {
|
|
mostFrequentHour = hour;
|
|
maxFrequency = freq;
|
|
}
|
|
});
|
|
|
|
// Need to handle the circular nature of time
|
|
// (e.g., average of 23:00 and 01:00 should be around midnight, not noon)
|
|
minutesArray.sort((a, b) => a - b);
|
|
|
|
// Check if we have times spanning across midnight
|
|
let useSortedMedian = false;
|
|
for (let i = 0; i < minutesArray.length - 1; i++) {
|
|
if (minutesArray[i + 1] - minutesArray[i] > 720) {
|
|
// More than 12 hours apart
|
|
useSortedMedian = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
let avgMinutesSinceMidnight;
|
|
|
|
if (useSortedMedian) {
|
|
// Use median to avoid the midnight crossing issue
|
|
const mid = Math.floor(minutesArray.length / 2);
|
|
avgMinutesSinceMidnight =
|
|
minutesArray.length % 2 === 0
|
|
? (minutesArray[mid - 1] + minutesArray[mid]) / 2
|
|
: minutesArray[mid];
|
|
} else {
|
|
// Simple average works when times are clustered
|
|
avgMinutesSinceMidnight = totalMinutes / timestamps.length;
|
|
}
|
|
|
|
// Convert back to hours and minutes
|
|
const avgHour = Math.floor(avgMinutesSinceMidnight / 60) % 24;
|
|
const avgMinute = Math.floor(avgMinutesSinceMidnight % 60);
|
|
|
|
// Format time nicely
|
|
const formattedTime = `${avgHour.toString().padStart(2, '0')}:${avgMinute.toString().padStart(2, '0')}`;
|
|
|
|
// Determine time of day
|
|
let timeOfDay: string;
|
|
let description: string;
|
|
|
|
if (avgHour >= 5 && avgHour < 12) {
|
|
timeOfDay = 'morning';
|
|
description = 'Morning';
|
|
} else if (avgHour >= 12 && avgHour < 17) {
|
|
timeOfDay = 'afternoon';
|
|
description = 'Afternoon';
|
|
} else if (avgHour >= 17 && avgHour < 21) {
|
|
timeOfDay = 'evening';
|
|
description = 'Evening';
|
|
} else {
|
|
timeOfDay = 'night';
|
|
description = 'Night';
|
|
}
|
|
|
|
return {
|
|
hour: avgHour,
|
|
minute: avgMinute,
|
|
formattedTime,
|
|
description,
|
|
timeOfDay,
|
|
earliest,
|
|
latest,
|
|
mostFrequentHour,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format a timestamp as a relative time (e.g., "2 hours ago")
|
|
*/
|
|
export function formatRelativeTime(timestamp: Date | string | number): string {
|
|
try {
|
|
const date = new Date(timestamp);
|
|
return formatDistanceToNow(date, { addSuffix: true });
|
|
} catch (e) {
|
|
return 'Invalid date';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Group timestamps by hour of day
|
|
*/
|
|
export function getHourDistribution(timestamps: Date[]): number[] {
|
|
const hours = new Array(24).fill(0);
|
|
|
|
timestamps.forEach((timestamp) => {
|
|
const date = new Date(timestamp);
|
|
const hour = date.getHours();
|
|
hours[hour]++;
|
|
});
|
|
|
|
return hours;
|
|
}
|
|
|
|
/**
|
|
* Get color for time of day visualization
|
|
*/
|
|
export function getTimeColor(hour: number): string {
|
|
if (hour >= 5 && hour < 12) return '#FFEB3B'; // morning - yellow
|
|
if (hour >= 12 && hour < 17) return '#FF9800'; // afternoon - orange
|
|
if (hour >= 17 && hour < 21) return '#3F51B5'; // evening - indigo
|
|
return '#263238'; // night - dark blue-grey
|
|
}
|