Skip to content

Commit 2503016

Browse files
committed
🐛 Convert Yr provider values to imperial units
Fix #36 met.no returns only SI/metric data and has no unit query params, so the Yr provider must convert client-side — previously it kept the raw metric numbers but the formatter labeled them as imperial. - Convert temperature, feels-like, dew point, wind, and precipitation when imperial units are requested (pressure stays hPa) - Add ms_to_mph and mm_to_inch helpers in weather::tools - Pass the unit-appropriate temperature to dew_point, fixing a latent °C-as-°F mismatch - Add an imperial parsing test alongside the metric one
1 parent a07b20d commit 2503016

2 files changed

Lines changed: 96 additions & 17 deletions

File tree

src/weather/providers/yr.rs

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use super::open_meteo::OpenMeteo;
22
use crate::config::Config;
33
use crate::display::translations::ll;
44
use crate::errors::RustormyError;
5-
use crate::models::{Language, Location, Provider, Weather, WeatherConditionIcon};
6-
use crate::weather::tools::{apparent_temperature, dew_point};
5+
use crate::models::{Language, Location, Provider, Units, Weather, WeatherConditionIcon};
6+
use crate::weather::tools::{apparent_temperature, c_to_f, dew_point, mm_to_inch, ms_to_mph};
77
use crate::weather::{GetWeather, LookUpCity, http};
88
use reqwest::blocking::Client;
99
use serde::{Deserialize, Serialize};
@@ -90,6 +90,7 @@ impl YrResponse {
9090
config: &Config,
9191
location: &Location,
9292
) -> Result<Weather, RustormyError> {
93+
let units = config.units();
9394
let timeseries = self
9495
.first_timeseries()
9596
.ok_or(RustormyError::ApiReturnedError(
@@ -109,9 +110,33 @@ impl YrResponse {
109110
let icon = symbol_code_to_icon(symbol_code);
110111
let is_day = symbol_code_to_is_day(symbol_code);
111112

113+
let feels_like_c = apparent_temperature(
114+
details.air_temperature,
115+
details.wind_speed,
116+
details.relative_humidity,
117+
);
118+
let precipitation_mm = details
119+
.precipitation_amount
120+
.unwrap_or_else(|| next_hours.details.precipitation_amount.unwrap_or(0.0));
121+
122+
let (temperature, feels_like, wind_speed, precipitation) = match units {
123+
Units::Metric => (
124+
details.air_temperature,
125+
feels_like_c,
126+
details.wind_speed,
127+
precipitation_mm,
128+
),
129+
Units::Imperial => (
130+
c_to_f(details.air_temperature),
131+
c_to_f(feels_like_c),
132+
ms_to_mph(details.wind_speed),
133+
mm_to_inch(precipitation_mm),
134+
),
135+
};
136+
112137
Ok(Weather {
113-
temperature: details.air_temperature,
114-
wind_speed: details.wind_speed,
138+
temperature,
139+
wind_speed,
115140
wind_direction: details
116141
.wind_from_direction
117142
.ok_or(RustormyError::ApiReturnedError(
@@ -124,19 +149,9 @@ impl YrResponse {
124149
icon,
125150
humidity: details.relative_humidity.round() as u8,
126151
pressure: details.air_pressure_at_sea_level.round() as u32,
127-
dew_point: dew_point(
128-
details.air_temperature,
129-
details.relative_humidity,
130-
config.units(),
131-
),
132-
feels_like: apparent_temperature(
133-
details.air_temperature,
134-
details.wind_speed,
135-
details.relative_humidity,
136-
),
137-
precipitation: details
138-
.precipitation_amount
139-
.unwrap_or_else(|| next_hours.details.precipitation_amount.unwrap_or(0.0)),
152+
dew_point: dew_point(temperature, details.relative_humidity, units),
153+
feels_like,
154+
precipitation,
140155
location: location.clone(),
141156
})
142157
}
@@ -496,4 +511,58 @@ mod test {
496511
assert_eq!(weather.dew_point, 5.4);
497512
assert_eq!(weather.precipitation, 1.2);
498513
}
514+
515+
#[test]
516+
fn test_parse_yr_response_imperial() {
517+
use crate::config::FormatterConfig;
518+
use crate::models::Units;
519+
520+
let data: YrResponse =
521+
serde_json::from_str(TEST_API_RESPONSE).expect("Failed to parse JSON");
522+
let mut config = Config::default();
523+
config.set_format(FormatterConfig {
524+
units: Units::Imperial,
525+
..Default::default()
526+
});
527+
let weather = data
528+
.into_weather(
529+
&config,
530+
&Location {
531+
name: "Test Location".to_string(),
532+
latitude: 0.0,
533+
longitude: 0.0,
534+
},
535+
)
536+
.expect("Failed to convert to Weather");
537+
538+
// Fixture is 6.4 °C, 7.3 m/s, 1.2 mm precip — all must be converted to imperial.
539+
assert!(
540+
(weather.temperature - 43.52).abs() < 0.01,
541+
"temperature: {}",
542+
weather.temperature
543+
);
544+
assert!(
545+
(weather.feels_like - 32.54).abs() < 0.05,
546+
"feels_like: {}",
547+
weather.feels_like
548+
);
549+
assert!(
550+
(weather.dew_point - 41.8).abs() < 0.05,
551+
"dew_point: {}",
552+
weather.dew_point
553+
);
554+
assert!(
555+
(weather.wind_speed - 16.3).abs() < 0.05,
556+
"wind_speed: {}",
557+
weather.wind_speed
558+
);
559+
assert!(
560+
(weather.precipitation - 0.05).abs() < 0.001,
561+
"precipitation: {}",
562+
weather.precipitation
563+
);
564+
// Humidity and pressure are unit-independent.
565+
assert_eq!(weather.humidity, 94);
566+
assert_eq!(weather.pressure, 985);
567+
}
499568
}

src/weather/tools.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ pub fn kph_to_ms(kph: f64) -> f64 {
4646
(kph / 3.6 * 10.0).round() / 10.0
4747
}
4848

49+
/// Convert m/s to mph, rounded to 1 decimal place
50+
pub fn ms_to_mph(ms: f64) -> f64 {
51+
(ms * 2.236_936 * 10.0).round() / 10.0
52+
}
53+
54+
/// Convert mm to inches, rounded to 2 decimal places
55+
pub fn mm_to_inch(mm: f64) -> f64 {
56+
(mm / 25.4 * 100.0).round() / 100.0
57+
}
58+
4959
/// Map OpenWeatherMap-style weather codes to icons.
5060
/// Used by `OpenWeatherMap` and `WeatherBit` (same code scheme).
5161
pub fn owm_code_to_icon(code: u32) -> WeatherConditionIcon {

0 commit comments

Comments
 (0)