Why API Responses Give You Weather Code 1183 at 3am: Understanding Condition Code Assignment at Night

The Condition Code That Shouldn’t Be There

You’re pulling real-time weather for a location at 3am and the response comes back with condition code 1183 — “Light rain”. Fine. But your app is also rendering a sunny icon because you forgot to check is_day, and now a user is looking at bright sunshine at midnight. That’s the obvious mistake. The less obvious one: sky-condition codes are assigned differently depending on time of day, and the logic behind that split isn’t documented anywhere you’d easily stumble across it.

This post covers how we map raw observations to WeatherAPI condition codes, what the day/night split actually means for that assignment, and where it produces results that look wrong but aren’t.

How We Map Raw Observations to Condition Codes

WeatherAPI uses its own numeric condition code system — around 50 distinct codes covering everything from blizzards to clear sky. We build that mapping ourselves from raw METAR observations and model output rather than licensing a third-party layer. That matters because every mapping decision is one we made deliberately, or occasionally discovered we’d gotten wrong and fixed.

A METAR observation gives you present weather codes (RA, SN, FG, TSRA), sky condition groups (FEW, SCT, BKN, OVC), and visibility. From those raw elements we derive a single condition code for a given location and time. The wrinkle: the METAR spec carries no concept of day or night — that’s external context we compute from the location’s solar position. And the condition code you get back depends on which version of the code is active at that moment.

Most of our condition codes exist in pairs. Code 1000 is “Clear” regardless of the hour, but when is_day is 0 the icon resolves to a moon. Code 1003 (“Partly cloudy”) works the same way — one numeric value, separate day and night icons, with is_day driving icon selection. Straightforward enough.

Where it gets messier is precipitation codes. 1183 and its neighbors (1180, 1186, 1189) are all “Light rain” variants — the numeric value encodes intensity and duration, not time of day. These don’t split by day or night. But the sky-condition codes sitting above them do. A “Partly cloudy” sky at night is still 1003; the background rendering logic is supposed to swap in a night icon. If you consume the icon URL field directly from our response, this is handled — we serve the correct night icon URL when is_day is 0. If you’re mapping codes to your own icon set, that logic is on you to implement.

The Actual Day/Night Cutoff Problem

Solar position gives us a clean is-day / is-night boundary, but real sky conditions don’t respect that cutoff. The edge cases that actually bite people:

  • Astronomical twilight windows: just after official sunrise or before sunset, is_day flips to 1 even though it’s still quite dark — especially in winter at higher latitudes. App logic that says “show sunny icon if is_day and condition == 1000” will fire shortly after astronomical sunrise, which can mean a bright icon while it’s still effectively dark outside. is_day is not the same as “it looks like daytime to a human right now.”
  • Model vs. METAR source switching: at night in regions with sparse METAR coverage, we fall back to model output (GFS or NAM grid data, depending on region) more than we do during the day. Model output doesn’t give you METAR-style present weather codes — it gives you precipitation rate, cloud fraction, and similar continuous fields, which we then threshold into condition codes. The thresholds are tuned to match METAR behavior, but they’re not identical. You can see a METAR-sourced reading at dusk and a model-sourced reading two hours later that produces a different condition code for essentially the same sky state. Not wrong, but inconsistent if you’re logging conditions over time and trying to build a clean record.

Why “Overcast” at Night Is Still Code 1009

This one confuses developers who dig into the condition text fields. Code 1009 is “Overcast” and has no nighttime variant because a fully overcast sky looks the same day or night from the ground — 8/8 cloud cover blocks the moon as thoroughly as the sun. The day/night icon distinction exists for partly-cloudy conditions where you can actually see what’s behind the clouds. For full overcast, it doesn’t. We kept the code structure simple: overcast is overcast, the icon doesn’t change by time of day.

“Partly cloudy” (1003) and “Cloudy” (1006) do get separate day/night icons in the icon URL response, but the numeric code value is the same either way. If you’re building your own rendering layer and relying on the code number alone to pick an icon, you also need to consume is_day. The icon URL field in the response handles this for you if you let it.

Where This Actually Causes Problems in Production

For most apps — weather widgets, travel apps, general consumer interfaces — none of this matters because they consume the icon URL directly and don’t do their own code-to-icon mapping. The issue surfaces when:

  • You’re storing condition codes in a database and later rendering icons from stored codes alone, without also storing is_day.
  • You’re building conditional logic on code ranges. The range 1000–1030 covers all cloud-cover conditions. Logic that says “if code < 1030, it’s not precipitating” holds regardless of day or night — but if you also try to infer a sky description from the code without is_day context, you’ll produce the wrong string for partly cloudy conditions roughly half the time.
  • You’re building a push notification system that sends “clear skies tonight” vs “clear skies today.” That phrase swap requires evaluating is_day at the time the forecast hour applies, not the time you send the notification.

Always Store is_day Alongside the Code

If you’re persisting weather responses — for historical lookup, offline display, anything that isn’t pure real-time rendering — store is_day next to condition.code. They’re a pair. Treating the condition code as a standalone value that carries full rendering context is the source of most of the confusion we see in support emails about wrong icons or condition descriptions that don’t match the sky.

The forecast endpoint gives you is_day per hourly slot. The real-time endpoint gives it at the top level. Neither is hard to access. The pattern worth avoiding: storing condition codes in isolation and reconstructing time-of-day context later from a timestamp. Timezone offsets and DST make that reconstruction unreliable enough that it’s not worth attempting when the field is already sitting in the response.

One real edge case to plan for: during polar twilight — northern Norway, Alaska, anywhere above roughly 65°N — is_day can hold at 1 for 20+ consecutive hours, or at 0 for equally long stretches. Our solar position calculation handles this correctly, but UI logic that assumes is_day alternates daily will break at extreme latitudes. If your app serves those regions, test against coordinates above 65°N before you ship.

If you’re seeing condition codes that look mismatched to what you’d expect for the time of day, pull is_day from the same response and check whether your rendering layer is actually consuming it. The code itself is almost always correct — the display logic just isn’t using all the context the API already handed it.

Scroll to Top