Stats percentages are calculated with wrong reference timeframe
Describe the feature or enhancement
Unfortunately issue #2937 is not completely resolved yet. As I "do not have permissions to reopen this issue" I had to create this new issue:
Probably I have not been clear with the problem description, so I'll try again.
I will explain again by using the current timestamp 2025-11-23 19:07:00.000 (1763921220000) and the query Last 24 hours as an example.
Problem description
If we imagine that the traffic on a website today unfolds_exactly the same_ as it did yesterday the stats will show negative percentages as we always compare the current incomplete interval to the full interval from yesterday. As you have added an additional hour from the interval before we now compare the current interval (24 hours + 7 mins) to the complete interval from yesterday (25 hours + 0 mins). Ideally, we would always compare intervals with the exact same length, so in the case that traffic today unfolds exactly like it did yesterday the stats would show 0 %.
Probably the issue title was not precise as well. The issue is not that the data is incomplete but rather that the comparison timeframe has the wrong endDate as it needs to have the same length as the current time interval.
Example
Having a look at the APIs I can see that in v3 you are now using a bigger timeframe than before.
The /pageviews endpoint call is working as expected as we want to show all the data we have in the charts.
Using the example from above the call should get all pageviews and sessions from 2025-11-22 18:00:00.000 to 2025-11-23 19:59:59.999 which it does:
Current endpoint call:
/pageviews?startAt=1763830800000&endAt=1763920799999&unit=hourThe /stats endpoint call should return the data from the last 25 hours (which it does) but it should compare it only to the respective timeframe from the previous date. Instead of comparing the current timeframe (2025-11-22 18:00:00.000 - 2025-11-23 19:59:59.000) The comparison timeframe should be from 2025-11-21 18:00:00.000 to 2025-11-22 19:07:00.000 (yesterday, same time as now). Currently, the startDate of the comparison timeframe is correct but the calculation of the endDate needs to include the currentDate / now.
Current endpoint call:
/stats?startAt=1763830800000&endAt=1763920799999&unit=hourImplementation
The following needs to be changed to cover the case when now / currentDate is before the endDate:
Stats endpoint call
The stats endpoint call should be changed to include the current timestamp:
/stats?startAt=1763830800000&endAt=1763920799999&now=1763921220000&unit=hour/stats endpoint
Map the new URL parameter now to the filter object:
New:
export function getRequestDateRange(query: Record<string, string>) {
const { startAt, endAt, now, unit, timezone } = query;
const startDate = new Date(+startAt);
const endDate = new Date(+endAt);
const currentDate = now ? new Date(+now) : null;
return {
startDate,
endDate,
currentDate,
timezone,
unit: getAllowedUnits(startDate, endDate).includes(unit)
? unit
: getMinimumUnit(startDate, endDate),
};
}Change the function for getting the comparison dates:
New:
export function getCompareDate(compare: string, startDate: Date, endDate: Date, currentDate?: Date) {
if (compare === 'yoy') {
return { compare, startDate: subYears(startDate, 1), endDate: subYears(endDate, 1) };
}
if (compare === 'prev') {
const diffStartEnd = differenceInMinutes(endDate, startDate);
if (currentDate && isBefore(currentDate, endDate)) {
const diffStartCurrent = differenceInMinutes(endDate, currentDate);
return { compare, startDate: subMinutes(startDate, diff), endDate: addMinutes(startDate, diffStartCurrent) };
}
return { compare, startDate, endDate: subMinutes(endDate, diff) };
}
return {};
}And finally change the call of the above function to include the currentDate:
New:
const { startDate, endDate } = getCompareDate('prev', filters.startDate, filters.endDate, filters.currentDate);Alternatively, one could add the now parameter to all calls but that would probably diminish the effect of caching.
I'd gladly setup a PR if you want me to.
Source: umami-software/umami