Precipitation Accumulation Windows: Why hourly_precip_mm Doesn’t Add Up to daily_precip_mm (And How to Work Around It)

The numbers don’t add up, and that’s not a bug

If you’ve ever summed hourly[*].precip_mm across a full day and compared it to day.totalprecip_mm in a WeatherAPI forecast response, you’ve probably noticed they don’t match. Sometimes by a small amount. Sometimes by enough to matter — 3mm vs 1.8mm, say, which is a significant gap if you’re triggering an irrigation hold or flagging a construction delay.

This isn’t a data quality issue. It’s an accumulation window mismatch, and it bites nearly every developer who builds anything beyond a simple weather display.

What each field is actually measuring

The daily totalprecip_mm figure comes from NWP model output — HRRR, GFS, or ECMWF depending on location and forecast range — aggregated over a calendar day in the requested location’s local timezone. That sounds straightforward. The hourly figures are snapshots of expected precipitation within that specific hour, not a cumulative running total, and they’re derived from the same model output at a slightly different stage of the post-processing pipeline.

The gap comes from a few places:

  • Timezone boundary handling. If you’re requesting data for Auckland (UTC+13) and processing the JSON in UTC, the hours you sum may straddle two local calendar days in ways that aren’t obvious from the timestamps alone.
  • Model initialization offsets. GFS runs at 00z, 06z, 12z, and 18z. A “day” of hourly data for a local timezone may be assembled from two different model runs — say 00z and 06z — and the seams between them don’t always blend cleanly when you sum naively.
  • Rounding at different aggregation stages. Hourly values are typically stored at two decimal places. Sum 24 of them and compounded rounding error is real, though usually small. The daily figure is computed from the raw model accumulation before that rounding step.

The third one is minor. The first two are not.

A concrete example of the timezone problem

Say you’re building a dashboard for a user in Kolkata (IST, UTC+5:30). Your server pulls forecast data and processes it in UTC. The local day runs from 18:30 UTC the previous calendar day to 18:30 UTC of the target date. The API response returns hourly objects keyed by local datetime strings — 2024-07-15 00:00 through 2024-07-15 23:00 in local time — but if you’re slicing those by UTC hour on your backend, you’ll cut the window wrong.

The half-hour offset in IST makes this worse. UTC+5:30 doesn’t fit neatly into any hourly UTC bucket, so the first and last hours of the local day are always partial from a UTC perspective. The daily total in the response accounts for the full local calendar day correctly. Summing hourly values without respecting that offset does not.

The fix: always use location.localtime and the location.tz_id field from the response — not your server’s clock, not a hardcoded offset — to define which hourly rows belong to which local day.

Which number should you actually trust?

For total daily accumulation, use day.totalprecip_mm. It’s computed from the raw model accumulation across the correct local calendar day before any hourly-level rounding. If you’re checking whether a threshold is crossed — 10mm for a flood risk flag, for example — this is the right field.

For within-day timing, the hourly values are what you want. If you’re determining whether rain will fall during a specific 2-hour outdoor event window, hourly[n].precip_mm and hourly[n].chance_of_rain together are far more useful than a daily total. The daily figure tells you nothing about whether 14mm is spread across 24 hours of drizzle or dumped in a 2-hour afternoon storm.

Don’t reconstruct daily totals by summing hourly values. You’ll get close but not exact, and “close” is the kind of thing that produces subtle production bugs that are hard to repro.

Handling this correctly in code

Here’s a pattern in Python that works reliably for timezone-aware daily aggregation when you need to verify or cross-reference accumulation windows:

from datetime import datetime
import pytz

def get_precip_for_local_day(forecast_day, tz_id):
    """
    Returns the daily total and hourly breakdown
    for a specific forecast day, timezone-aware.
    """
    tz = pytz.timezone(tz_id)
    daily_total = forecast_day['day']['totalprecip_mm']

    hourly_breakdown = []
    for hour in forecast_day['hour']:
        local_dt = datetime.strptime(hour['time'], '%Y-%m-%d %H:%M')
        local_dt = tz.localize(local_dt)
        hourly_breakdown.append({
            'local_time': local_dt.isoformat(),
            'precip_mm': hour['precip_mm'],
            'chance_of_rain': hour['chance_of_rain']
        })

    # Use daily_total for threshold checks;
    # hourly_breakdown for timing decisions
    return daily_total, hourly_breakdown

Don’t sum hourly_breakdown and expect it to equal daily_total. Use each for what it’s actually authoritative about.

Where this causes real problems

Agricultural applications are the most common place this bites. An irrigation controller deciding whether to skip a cycle based on forecast precipitation should pull from totalprecip_mm. But a system also trying to avoid running irrigation during rain — not just because of it — needs the hourly window. Mixing them carelessly means you might skip a cycle because 12mm is forecast, then run irrigation at 2pm because the hourly slot shows 0.1mm and the threshold looks fine. Both numbers are correct. They’re answering different questions.

Construction scheduling has the same shape: “will there be more than 5mm today” is a daily question; “will it be raining at 09:00 when the concrete pour starts” is an hourly one. They’re not interchangeable, and treating them as if they are is where things quietly break.

The part most integrations skip

Hourly precipitation figures beyond roughly 48–72 hours are increasingly unreliable regardless of accumulation questions. GFS runs at approximately 13km horizontal resolution and ECMWF’s operational deterministic model at around 9km; at those scales, convective precipitation — thunderstorms, heavy showers — can be misplaced by tens of kilometers and several hours. The daily total captures the model’s best estimate of how much will fall and is somewhat more stable, but it’s more confident in that number than in when it falls. For forecast days 4–14, hourly precipitation is directional, not precise.

If you’re building anything that acts on precipitation timing rather than just existence, limit automated decisions to the 48-hour window where the hourly signal is actually meaningful. Beyond that, flag or dampen automated actions rather than treating day-7 hour-level output as reliable scheduling input.

Pull a week of historical forecast data for a location with convective summer rain — Miami, Mumbai, Brisbane — and compare the sum of 24 hourly values against the daily total for each day. The distribution of that discrepancy will tell you more about whether your specific use case needs a correction layer than any general advice here will.

Scroll to Top