From 5352457032543fabac028f82805c7015fa78e158 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 18 Dec 2025 16:26:40 -0800 Subject: [PATCH] use openweathermap weather codes --- src/render/ansi.zig | 2 +- src/render/custom.zig | 12 +++---- src/render/json.zig | 2 +- src/render/line.zig | 28 +++++++-------- src/render/v2.zig | 4 +-- src/weather/MetNo.zig | 36 +++++++++---------- src/weather/Mock.zig | 2 +- src/weather/types.zig | 81 +++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 121 insertions(+), 46 deletions(-) diff --git a/src/render/ansi.zig b/src/render/ansi.zig index 831566f..d5aa230 100644 --- a/src/render/ansi.zig +++ b/src/render/ansi.zig @@ -35,7 +35,7 @@ test "render with imperial units" { .temp_c = 10.0, .temp_f = 50.0, .condition = "Clear", - .weather_code = 113, + .weather_code = .clear, .humidity = 60, .wind_kph = 16.0, .wind_dir = "N", diff --git a/src/render/custom.zig b/src/render/custom.zig index 4b2cb36..d39879a 100644 --- a/src/render/custom.zig +++ b/src/render/custom.zig @@ -8,8 +8,8 @@ const weather_icons = [_][]const u8{ "☁️", // 300-399 }; -fn getWeatherIcon(code: u16) []const u8 { - const idx = @min(code / 100, weather_icons.len - 1); +fn getWeatherIcon(code: types.WeatherCode) []const u8 { + const idx = @min(@intFromEnum(code) / 100, weather_icons.len - 1); return weather_icons[idx]; } @@ -85,7 +85,7 @@ test "render custom format with location and temp" { .temp_c = 7.0, .temp_f = 44.6, .condition = "Overcast", - .weather_code = 122, + .weather_code = .clouds_overcast, .humidity = 76, .wind_kph = 11.0, .wind_dir = "NNE", @@ -112,7 +112,7 @@ test "render custom format with newline" { .temp_c = 10.0, .temp_f = 50.0, .condition = "Clear", - .weather_code = 113, + .weather_code = .clear, .humidity = 65, .wind_kph = 8.0, .wind_dir = "E", @@ -138,7 +138,7 @@ test "render custom format with humidity and pressure" { .temp_c = 5.0, .temp_f = 41.0, .condition = "Cloudy", - .weather_code = 119, + .weather_code = .clouds_overcast, .humidity = 85, .wind_kph = 12.0, .wind_dir = "W", @@ -165,7 +165,7 @@ test "render custom format with imperial units" { .temp_c = 10.0, .temp_f = 50.0, .condition = "Clear", - .weather_code = 113, + .weather_code = .clear, .humidity = 60, .wind_kph = 16.0, .wind_dir = "N", diff --git a/src/render/json.zig b/src/render/json.zig index 23ef0d9..7ed92fc 100644 --- a/src/render/json.zig +++ b/src/render/json.zig @@ -29,7 +29,7 @@ test "render json format" { .temp_c = 15.0, .temp_f = 59.0, .condition = "Partly cloudy", - .weather_code = 116, + .weather_code = .clouds_few, .humidity = 72, .wind_kph = 13.0, .wind_dir = "SW", diff --git a/src/render/line.zig b/src/render/line.zig index 288b214..4a26532 100644 --- a/src/render/line.zig +++ b/src/render/line.zig @@ -116,17 +116,17 @@ fn renderCustom(allocator: std.mem.Allocator, data: types.WeatherData, format: [ return output.toOwnedSlice(allocator); } -fn getConditionEmoji(code: u16) []const u8 { - return switch (code) { - 113 => "☀️", - 116 => "⛅️", - 119, 122 => "☁️", - 143, 248, 260 => "🌫", - 176, 263, 266, 293, 296 => "🌦", - 185, 281, 284, 311, 314, 317, 350, 362, 365, 374, 377 => "🌧", - 200, 386, 389, 392, 395 => "⛈", - 227, 230, 320, 323, 326, 329, 332, 335, 338, 368, 371 => "🌨", - 179, 182 => "❄️", +fn getConditionEmoji(code: types.WeatherCode) []const u8 { + return switch (@intFromEnum(code)) { + 800 => "☀️", // Clear + 801, 802 => "⛅️", // Few/scattered clouds + 803, 804 => "☁️", // Broken/overcast clouds + 701, 741 => "🌫", // Mist/fog + 300...321 => "🌦", // Drizzle + 500...531 => "🌧", // Rain + 200...232 => "⛈", // Thunderstorm + 611...616 => "❄️", // Sleet/freezing (check before snow) + 600...610, 617...622 => "🌨", // Snow else => "🌡️", }; } @@ -138,7 +138,7 @@ test "format 1" { .temp_c = 15.0, .temp_f = 59.0, .condition = "Clear", - .weather_code = 113, + .weather_code = .clear, .humidity = 65, .wind_kph = 10.0, .wind_dir = "N", @@ -162,7 +162,7 @@ test "custom format" { .temp_c = 15.0, .temp_f = 59.0, .condition = "Clear", - .weather_code = 113, + .weather_code = .clear, .humidity = 65, .wind_kph = 10.0, .wind_dir = "N", @@ -186,7 +186,7 @@ test "format 2 with imperial units" { .temp_c = 10.0, .temp_f = 50.0, .condition = "Cloudy", - .weather_code = 119, + .weather_code = .clouds_overcast, .humidity = 70, .wind_kph = 20.0, .wind_dir = "SE", diff --git a/src/render/v2.zig b/src/render/v2.zig index 6099c5d..20c0abb 100644 --- a/src/render/v2.zig +++ b/src/render/v2.zig @@ -76,7 +76,7 @@ test "render v2 format" { .temp_c = 12.0, .temp_f = 53.6, .condition = "Overcast", - .weather_code = 122, + .weather_code = .clouds_overcast, .humidity = 80, .wind_kph = 15.0, .wind_dir = "NW", @@ -105,7 +105,7 @@ test "render v2 format with imperial units" { .temp_c = 10.0, .temp_f = 50.0, .condition = "Clear", - .weather_code = 113, + .weather_code = .clear, .humidity = 65, .wind_kph = 16.0, .wind_dir = "N", diff --git a/src/weather/MetNo.zig b/src/weather/MetNo.zig index a22757f..834d7dd 100644 --- a/src/weather/MetNo.zig +++ b/src/weather/MetNo.zig @@ -126,29 +126,29 @@ fn parseMetNoResponse(allocator: std.mem.Allocator, coords: Coordinates, json: s }; } -fn symbolCodeToWeatherCode(symbol: []const u8) u16 { - if (std.mem.indexOf(u8, symbol, "clearsky")) |_| return 113; - if (std.mem.indexOf(u8, symbol, "fair")) |_| return 116; - if (std.mem.indexOf(u8, symbol, "partlycloudy")) |_| return 116; - if (std.mem.indexOf(u8, symbol, "cloudy")) |_| return 119; - if (std.mem.indexOf(u8, symbol, "fog")) |_| return 143; - if (std.mem.indexOf(u8, symbol, "rain")) |_| return 296; - if (std.mem.indexOf(u8, symbol, "sleet")) |_| return 362; - if (std.mem.indexOf(u8, symbol, "snow")) |_| return 338; - if (std.mem.indexOf(u8, symbol, "thunder")) |_| return 200; - return 113; +fn symbolCodeToWeatherCode(symbol: []const u8) types.WeatherCode { + if (std.mem.indexOf(u8, symbol, "clearsky")) |_| return .clear; + if (std.mem.indexOf(u8, symbol, "partlycloudy")) |_| return .clouds_scattered; + if (std.mem.indexOf(u8, symbol, "fair")) |_| return .clouds_few; + if (std.mem.indexOf(u8, symbol, "cloudy")) |_| return .clouds_overcast; + if (std.mem.indexOf(u8, symbol, "fog")) |_| return .fog; + if (std.mem.indexOf(u8, symbol, "thunder")) |_| return .thunderstorm; + if (std.mem.indexOf(u8, symbol, "rain")) |_| return .rain_moderate; + if (std.mem.indexOf(u8, symbol, "sleet")) |_| return .sleet; + if (std.mem.indexOf(u8, symbol, "snow")) |_| return .snow; + return .clear; } fn symbolCodeToCondition(symbol: []const u8) []const u8 { if (std.mem.indexOf(u8, symbol, "clearsky")) |_| return "Clear"; - if (std.mem.indexOf(u8, symbol, "fair")) |_| return "Fair"; if (std.mem.indexOf(u8, symbol, "partlycloudy")) |_| return "Partly cloudy"; + if (std.mem.indexOf(u8, symbol, "fair")) |_| return "Fair"; if (std.mem.indexOf(u8, symbol, "cloudy")) |_| return "Cloudy"; if (std.mem.indexOf(u8, symbol, "fog")) |_| return "Fog"; + if (std.mem.indexOf(u8, symbol, "thunder")) |_| return "Thunderstorm"; if (std.mem.indexOf(u8, symbol, "rain")) |_| return "Rain"; if (std.mem.indexOf(u8, symbol, "sleet")) |_| return "Sleet"; if (std.mem.indexOf(u8, symbol, "snow")) |_| return "Snow"; - if (std.mem.indexOf(u8, symbol, "thunder")) |_| return "Thunderstorm"; return "Clear"; } @@ -171,9 +171,9 @@ test "degreeToDirection" { } test "symbolCodeToWeatherCode" { - try std.testing.expectEqual(@as(u16, 113), symbolCodeToWeatherCode("clearsky_day")); - try std.testing.expectEqual(@as(u16, 116), symbolCodeToWeatherCode("fair_night")); - try std.testing.expectEqual(@as(u16, 119), symbolCodeToWeatherCode("cloudy")); - try std.testing.expectEqual(@as(u16, 296), symbolCodeToWeatherCode("lightrain")); - try std.testing.expectEqual(@as(u16, 338), symbolCodeToWeatherCode("snow")); + try std.testing.expectEqual(types.WeatherCode.clear, symbolCodeToWeatherCode("clearsky_day")); + try std.testing.expectEqual(types.WeatherCode.clouds_few, symbolCodeToWeatherCode("fair_night")); + try std.testing.expectEqual(types.WeatherCode.clouds_overcast, symbolCodeToWeatherCode("cloudy")); + try std.testing.expectEqual(types.WeatherCode.rain_moderate, symbolCodeToWeatherCode("lightrain")); + try std.testing.expectEqual(types.WeatherCode.snow, symbolCodeToWeatherCode("snow")); } diff --git a/src/weather/Mock.zig b/src/weather/Mock.zig index 15c7ec1..4cf0b36 100644 --- a/src/weather/Mock.zig +++ b/src/weather/Mock.zig @@ -70,7 +70,7 @@ test "mock weather provider" { .temp_c = 15.0, .temp_f = 59.0, .condition = "Clear", - .weather_code = 113, + .weather_code = .clear, .humidity = 65, .wind_kph = 10.0, .wind_dir = "N", diff --git a/src/weather/types.zig b/src/weather/types.zig index c6ed749..cc8af54 100644 --- a/src/weather/types.zig +++ b/src/weather/types.zig @@ -1,5 +1,80 @@ const std = @import("std"); +/// Weather condition codes based on OpenWeatherMap standard +/// https://openweathermap.org/weather-conditions +pub const WeatherCode = enum(u16) { + // Thunderstorm group (2xx) + thunderstorm_light_rain = 200, + thunderstorm_rain = 201, + thunderstorm_heavy_rain = 202, + thunderstorm_light = 210, + thunderstorm = 211, + thunderstorm_heavy = 212, + thunderstorm_ragged = 221, + thunderstorm_light_drizzle = 230, + thunderstorm_drizzle = 231, + thunderstorm_heavy_drizzle = 232, + + // Drizzle group (3xx) + drizzle_light = 300, + drizzle = 301, + drizzle_heavy = 302, + drizzle_rain_light = 310, + drizzle_rain = 311, + drizzle_rain_heavy = 312, + drizzle_shower_light = 313, + drizzle_shower = 314, + drizzle_shower_heavy = 321, + + // Rain group (5xx) + rain_light = 500, + rain_moderate = 501, + rain_heavy = 502, + rain_very_heavy = 503, + rain_extreme = 504, + rain_freezing = 511, + rain_shower_light = 520, + rain_shower = 521, + rain_shower_heavy = 522, + rain_shower_ragged = 531, + + // Snow group (6xx) + snow_light = 600, + snow = 601, + snow_heavy = 602, + sleet = 611, + sleet_shower_light = 612, + sleet_shower = 613, + rain_snow_light = 615, + rain_snow = 616, + snow_shower_light = 620, + snow_shower = 621, + snow_shower_heavy = 622, + + // Atmosphere group (7xx) + mist = 701, + smoke = 711, + haze = 721, + dust_whirls = 731, + fog = 741, + sand = 751, + dust = 761, + ash = 762, + squall = 771, + tornado = 781, + + // Clear group (800) + clear = 800, + + // Clouds group (80x) + clouds_few = 801, // 11-25% + clouds_scattered = 802, // 25-50% + clouds_broken = 803, // 51-84% + clouds_overcast = 804, // 85-100% + + _, +}; + pub const WeatherError = error{ LocationNotFound, ApiError, @@ -31,7 +106,7 @@ pub const CurrentCondition = struct { temp_c: f32, temp_f: f32, condition: []const u8, - weather_code: u16, + weather_code: WeatherCode, humidity: u8, wind_kph: f32, wind_dir: []const u8, @@ -44,7 +119,7 @@ pub const ForecastDay = struct { max_temp_c: f32, min_temp_c: f32, condition: []const u8, - weather_code: u16, + weather_code: WeatherCode, hourly: []HourlyForecast, }; @@ -52,7 +127,7 @@ pub const HourlyForecast = struct { time: []const u8, temp_c: f32, condition: []const u8, - weather_code: u16, + weather_code: WeatherCode, wind_kph: f32, precip_mm: f32, };