litellm - 💡(How to fix) Fix [Bug]: /user/daily/activity overstates totals for single-day window in non-UTC timezones (timezone expansion not partitioned back to local day)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

Root cause (suspected)

Code Example

-- Customer SQL on May 11 (matches GCP within expected variance)
model                       | calls | total_spend_usd
----------------------------+-------+-----------------
 vertex_ai/claude-sonnet-4-6 |    24 |        0.930369
 vertex_ai/claude-haiku-4-5  |    13 |        0.057662
 claude-sonnet-4-6           |     1 |        0.000123

-- LiteLLM Usage page (May 11May 11): $8.7824 / 176 requests
-- GCP Billing (May 11): $1.10 after savings, $1.26 list
RAW_BUFFERClick to expand / collapse

Check for existing issues

  • I have searched the existing issues.

What happened?

The proxy /user/daily/activity endpoint (and the Usage page in the UI that consumes it) returns spend and request counts that are larger than what is actually in LiteLLM_DailyUserSpend for the user-requested date range, when the caller is in a non-UTC timezone.

Customer context: A customer was comparing LiteLLM-tracked spend for a single GCP project against GCP Billing for May 11, 2026 to validate spend tracking. The LiteLLM Usage page (Project Spend, "May 11 → May 11") showed $8.7824 across 176 requests. A direct SQL query against LiteLLM_DailyUserSpend for May 11 only showed $0.99 across 38 calls, which matches GCP Billing closely (~$1.10 after savings). So spend capture is correct — the daily-activity endpoint is the source of the inflated number, and it eroded customer trust in the dashboard. Without doing the SQL query ourselves, the customer would have walked away believing LiteLLM was off by ~8x.

Root cause (suspected)

In litellm/proxy/management_endpoints/common_daily_activity.py:

  • _adjust_dates_for_timezone (line 382) intentionally expands the date range by ±1 day to capture UTC-stored records that overlap with the caller's local date range. For a Pacific caller requesting "May 11 → May 11", it queries date >= "2026-05-11" AND date <= "2026-05-12".
  • _build_where_conditions (line 422) applies that expanded range to LiteLLM_DailyUserSpend.date (stored as a string YYYY-MM-DD).
  • The endpoint then returns full UTC days for both — there is no partitioning step to attribute only the portion of UTC May 12's records that fall inside the caller's local May 11.

Net effect: a "May 11 → May 11" PST query returns all of UTC May 11 + all of UTC May 12. Both the dashboard total and the chart bar count reflect the wider window, even though the date picker says a single day.

Steps to Reproduce

  1. Run the proxy with PostgreSQL and a non-UTC client (e.g., browser in PST).
  2. Generate Vertex AI / OpenAI traffic on date = 2026-05-11 UTC and date = 2026-05-12 UTC.
  3. Open the Usage page in the dashboard and select start = May 11, end = May 11.
  4. Note the total spend and request count.
  5. Compare against SELECT sum(spend), sum(api_requests) FROM "LiteLLM_DailyUserSpend" WHERE date = '2026-05-11';.

Observed: the UI/endpoint total is materially larger than the SQL total for the same calendar day. Expected: UI total for a "May 11 → May 11" selection reflects only May 11 (either UTC May 11 or, if we want to honor local timezone, only records attributable to the caller's local May 11).

Suggested fix

A few options, increasing in correctness:

  1. Document that the date window is UTC-anchored, and stop expanding by ±1 day. Simplest, least surprising.
  2. Keep the expansion, but after fetching, partition records by aligning their UTC date against the caller's local day and drop/scale ones that fall outside.
  3. Store daily aggregates with an explicit UTC midnight DateTime (not a YYYY-MM-DD string), so range filters are unambiguous and partial-day attribution becomes feasible.

Relevant log output

-- Customer SQL on May 11 (matches GCP within expected variance)
model                       | calls | total_spend_usd
----------------------------+-------+-----------------
 vertex_ai/claude-sonnet-4-6 |    24 |        0.930369
 vertex_ai/claude-haiku-4-5  |    13 |        0.057662
 claude-sonnet-4-6           |     1 |        0.000123

-- LiteLLM Usage page (May 11 → May 11): $8.7824 / 176 requests
-- GCP Billing (May 11): $1.10 after savings, $1.26 list

What part of LiteLLM is this about?

UI Dashboard / Proxy (/user/daily/activity endpoint)

What LiteLLM version are you on?

Latest as of 2026-05-12. Customer is on a recent stable; confirming exact version.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

litellm - 💡(How to fix) Fix [Bug]: /user/daily/activity overstates totals for single-day window in non-UTC timezones (timezone expansion not partitioned back to local day)