use jiff::tz::TimeZone;
use crate::zmanim::types::error::ZmanimError;
#[derive(Debug, Clone, PartialEq)]
pub struct Location {
pub latitude: f64,
pub longitude: f64,
pub elevation: f64,
pub timezone: Option<TimeZone>,
}
impl Location {
pub fn new(latitude: f64, longitude: f64, elevation: f64, timezone: Option<TimeZone>) -> Result<Self, ZmanimError> {
if timezone.is_none() && Self::near_anti_meridian(longitude) {
return Err(ZmanimError::TimeZoneRequired);
}
if longitude.abs() > 180.0 || longitude.is_nan() {
return Err(ZmanimError::InvalidLongitude);
}
if latitude.abs() > 90.0 || latitude.is_nan() {
return Err(ZmanimError::InvalidLatitude);
}
if elevation.is_nan() || elevation < 0.0 {
return Err(ZmanimError::InvalidElevation);
}
Ok(Self {
latitude,
longitude,
elevation,
timezone,
})
}
pub(crate) fn near_anti_meridian(longitude: f64) -> bool {
const ANTI_MERIDIAN_THRESHOLD: f64 = 150.0;
longitude.abs() > ANTI_MERIDIAN_THRESHOLD
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Location {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"Location {{ latitude: {}, longitude: {}, elevation: {}, has_timezone: {} }}",
self.latitude,
self.longitude,
self.elevation,
self.timezone.is_some(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use jiff::tz::TimeZone;
#[test]
fn test_location_rejects_anti_meridian_without_timezone() {
let location = Location::new(0.0, 150.1, 0.0, None);
assert!(location.is_err());
}
#[test]
fn test_location_rejects_out_of_range_coords() {
let bad_longitude = Location::new(0.0, 181.0, 0.0, Some(TimeZone::UTC));
assert!(bad_longitude.is_err());
let bad_latitude = Location::new(91.0, 0.0, 0.0, Some(TimeZone::UTC));
assert!(bad_latitude.is_err());
}
#[test]
fn test_location_rejects_negative_elevation() {
let location = Location::new(0.0, 0.0, -1.0, Some(TimeZone::UTC));
assert!(location.is_err());
}
}