#3786·umami

Stats percentages are calculated with wrong reference timeframe

Author: schlagmichdochCreated Nov 23, 2025Updated Sep 16, 2026
Labelsenhancement

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:

url
/pageviews?startAt=1763830800000&endAt=1763920799999&unit=hour

The /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:

url
/stats?startAt=1763830800000&endAt=1763920799999&unit=hour

Implementation

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:

url
/stats?startAt=1763830800000&endAt=1763920799999&now=1763921220000&unit=hour

/stats endpoint

Map the new URL parameter now to the filter object:

Old: https://github.com/umami-software/umami/blob/aaa1f9dc58feafe6af54248c5f0611112786fddf/src/lib/request.ts#L54-L68

New:

typescript
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:

Old: https://github.com/umami-software/umami/blob/aaa1f9dc58feafe6af54248c5f0611112786fddf/src/lib/date.ts#L298-L310

New:

typescript
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:

Old: https://github.com/umami-software/umami/blob/aaa1f9dc58feafe6af54248c5f0611112786fddf/src/app/api/websites/%5BwebsiteId%5D/stats/route.ts#L34

New:

typescript
  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.