Time Distribution Analysis
Analyze hourly and daily distribution of timestamps
Source Code
import fs from "fs";
import path from "path";
const [inputPath, dateField, outputPath] = process.argv.slice(2);
if (!inputPath || !dateField || !outputPath) {
console.error("Usage: stdlib.time.distribute <inputPath> <dateField> <outputPath>");
process.exit(1);
}
const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const HOUR_LABELS = Array.from({ length: 24 }, (_, i) => `${String(i).padStart(2, "0")}:00`);
/**
* Get nested field value
*/
function getField(obj, fieldPath) {
const parts = fieldPath.split(".");
let value = obj;
for (const part of parts) {
if (value == null) return undefined;
value = value[part];
}
return value;
}
try {
console.log(`Reading ${inputPath}...`);
const raw = fs.readFileSync(inputPath, "utf-8");
const data = JSON.parse(raw);
const items = Array.isArray(data)
? data
: data.items || data.results || data.messages || [];
if (!Array.isArray(items)) {
console.error("Input must be a JSON array or object with array property");
process.exit(1);
}
console.log(`Analyzing ${items.length} timestamps...`);
// Initialize distributions
const hourDistribution = new Array(24).fill(0);
const dayDistribution = new Array(7).fill(0);
const hourlyByDay = Array.from({ length: 7 }, () => new Array(24).fill(0));
let validCount = 0;
let earliestDate = null;
let latestDate = null;
for (const item of items) {
const dateValue = getField(item, dateField);
if (!dateValue) continue;
const d = new Date(dateValue);
if (isNaN(d.getTime())) continue;
validCount++;
hourDistribution[d.getHours()]++;
dayDistribution[d.getDay()]++;
hourlyByDay[d.getDay()][d.getHours()]++;
if (!earliestDate || d < earliestDate) earliestDate = d;
if (!latestDate || d > latestDate) latestDate = d;
}
// Find peaks
const peakHour = hourDistribution.indexOf(Math.max(...hourDistribution));
const peakDay = dayDistribution.indexOf(Math.max(...dayDistribution));
const quietHour = hourDistribution.indexOf(Math.min(...hourDistribution.filter(x => x > 0)) || 0);
const quietDay = dayDistribution.indexOf(Math.min(...dayDistribution.filter(x => x > 0)) || 0);
// Categorize time periods
const morningCount = hourDistribution.slice(6, 12).reduce((a, b) => a + b, 0);
const afternoonCount = hourDistribution.slice(12, 18).reduce((a, b) => a + b, 0);
const eveningCount = hourDistribution.slice(18, 22).reduce((a, b) => a + b, 0);
const nightCount = hourDistribution.slice(22, 24).reduce((a, b) => a + b, 0) +
hourDistribution.slice(0, 6).reduce((a, b) => a + b, 0);
const weekdayCount = dayDistribution.slice(1, 6).reduce((a, b) => a + b, 0);
const weekendCount = dayDistribution[0] + dayDistribution[6];
// Build top hours and days
const topHours = hourDistribution
.map((count, hour) => ({ hour: HOUR_LABELS[hour], count }))
.sort((a, b) => b.count - a.count)
.slice(0, 5);
const topDays = dayDistribution
.map((count, day) => ({ day: DAY_NAMES[day], count }))
.sort((a, b) => b.count - a.count);
const result = {
analyzedAt: new Date().toISOString(),
dateField,
totalItems: items.length,
validTimestamps: validCount,
dateRange: earliestDate && latestDate ? {
earliest: earliestDate.toISOString(),
latest: latestDate.toISOString(),
spanDays: Math.ceil((latestDate - earliestDate) / (24 * 60 * 60 * 1000)),
} : null,
hourly: {
distribution: hourDistribution,
labels: HOUR_LABELS,
peakHour: HOUR_LABELS[peakHour],
peakCount: hourDistribution[peakHour],
quietHour: HOUR_LABELS[quietHour],
topHours,
},
daily: {
distribution: dayDistribution,
labels: DAY_NAMES,
peakDay: DAY_NAMES[peakDay],
peakCount: dayDistribution[peakDay],
quietDay: DAY_NAMES[quietDay],
topDays,
},
periods: {
morning: { count: morningCount, percent: Math.round(morningCount / validCount * 100) || 0 },
afternoon: { count: afternoonCount, percent: Math.round(afternoonCount / validCount * 100) || 0 },
evening: { count: eveningCount, percent: Math.round(eveningCount / validCount * 100) || 0 },
night: { count: nightCount, percent: Math.round(nightCount / validCount * 100) || 0 },
},
weekVsWeekend: {
weekday: { count: weekdayCount, percent: Math.round(weekdayCount / validCount * 100) || 0 },
weekend: { count: weekendCount, percent: Math.round(weekendCount / validCount * 100) || 0 },
},
patterns: {
isNightOwl: nightCount > morningCount,
isEarlyBird: morningCount > eveningCount,
prefersWeekends: weekendCount / 2 > weekdayCount / 5,
},
heatmap: hourlyByDay.map((hours, day) => ({
day: DAY_NAMES[day],
hours,
})),
};
// Ensure output directory exists
const dir = path.dirname(outputPath);
if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(result, null, 2));
console.log(`\n✓ Analyzed ${validCount} timestamps`);
console.log(` Date range: ${result.dateRange?.spanDays || 0} days`);
console.log(` Peak hour: ${result.hourly.peakHour} (${result.hourly.peakCount} occurrences)`);
console.log(` Peak day: ${result.daily.peakDay} (${result.daily.peakCount} occurrences)`);
console.log(` Patterns: ${result.patterns.isNightOwl ? "Night owl" : result.patterns.isEarlyBird ? "Early bird" : "Balanced"}`);
console.log(` Written to: ${outputPath}`);
console.log(JSON.stringify({
success: true,
outputPath,
validTimestamps: validCount,
peakHour: result.hourly.peakHour,
peakDay: result.daily.peakDay,
patterns: result.patterns,
}));
} catch (error) {
console.error("Error:", error.message);
process.exit(1);
}