import { trimDate } from "./trimDate.js";
import { roundAndUseNull, useNull } from "./utils.js";
/**
* Calculate hourly (diurnal) statistics over recent days.
*
* Returns five arrays of hourly values:
* * hour -- Local-time hour of day [0–23]
* * count -- Number of non-missing values per hour
* * min -- Minimum value at each hour
* * mean -- Mean value at each hour
* * max -- Maximum value at each hour
*
* By default, the most recent 7 full days are used.
*
* @param {Array.<DateTime>} datetime - Hourly UTC timestamps as Luxon DateTime objects.
* @param {Array.<number>} x - Matching array of values (e.g. PM2.5).
* @param {string} timezone - Olson timezone string (e.g. "America/Los_Angeles").
* @param {number} dayCount - Number of days to include (default: 7).
* @returns {object} - Object with hour, count, min, mean, and max arrays.
*/
export function diurnalStats(datetime, x, timezone, dayCount = 7) {
// Trim to full local days
const trimmed = trimDate(datetime, x, timezone);
// Return empty result if no full days exist
if (
!Array.isArray(trimmed.datetime) ||
trimmed.datetime.length < 24 ||
trimmed.datetime.length !== trimmed.x.length
) {
return {
hour: [],
count: [],
min: [],
mean: [],
max: [],
};
}
// Use as many recent full days as possible, up to `dayCount`
const fullDayCount = trimmed.datetime.length / 24;
dayCount = fullDayCount < dayCount ? fullDayCount : dayCount;
const startIndex = trimmed.datetime.length - dayCount * 24;
// Clean data and mark valid values
const values = useNull(trimmed.x);
const validFlags = values.map((v) => (v === null ? 0 : 1));
const hour = [];
const hourlyCount = [];
const hourlyMin = [];
const hourlyMean = [];
const hourlyMax = [];
// For each hour of the day (0–23), compute stats across days
for (let h = 0; h < 24; h++) {
let min = Number.MAX_VALUE;
let max = Number.MIN_VALUE;
let count = 0;
let sum = null;
for (let d = 0; d < dayCount; d++) {
const index = startIndex + h + d * 24;
if (validFlags[index] === 1) {
if (sum === null) sum = 0;
min = values[index] < min ? values[index] : min;
max = values[index] > max ? values[index] : max;
sum += values[index];
count += 1;
}
}
hour[h] = h;
hourlyMin[h] = min === Number.MAX_VALUE ? null : min;
hourlyMax[h] = max === Number.MIN_VALUE ? null : max;
hourlyCount[h] = count;
hourlyMean[h] = sum === null ? null : sum / count;
}
// Round all numeric outputs and use null for missing
const roundedMin = roundAndUseNull(hourlyMin);
const roundedMean = roundAndUseNull(hourlyMean);
const roundedMax = roundAndUseNull(hourlyMax);
return {
hour: hour,
count: hourlyCount,
min: roundedMin,
mean: roundedMean,
max: roundedMax,
};
}