kosher_rust/zmanim/types/
location.rs1use jiff::tz::TimeZone;
2
3use crate::zmanim::types::error::ZmanimError;
4
5#[derive(Debug, Clone, PartialEq)]
7pub struct Location {
8 pub latitude: f64,
10 pub longitude: f64,
13 pub elevation: f64,
15 pub timezone: Option<TimeZone>,
18}
19
20impl Location {
21 pub fn new(latitude: f64, longitude: f64, elevation: f64, timezone: Option<TimeZone>) -> Result<Self, ZmanimError> {
29 if timezone.is_none() && Self::near_anti_meridian(longitude) {
30 return Err(ZmanimError::TimeZoneRequired);
31 }
32
33 if longitude.abs() > 180.0 || longitude.is_nan() {
34 return Err(ZmanimError::InvalidLongitude);
35 }
36 if latitude.abs() > 90.0 || latitude.is_nan() {
37 return Err(ZmanimError::InvalidLatitude);
38 }
39 if elevation.is_nan() || elevation < 0.0 {
40 return Err(ZmanimError::InvalidElevation);
41 }
42
43 Ok(Self {
44 latitude,
45 longitude,
46 elevation,
47 timezone,
48 })
49 }
50
51 pub(crate) fn near_anti_meridian(longitude: f64) -> bool {
52 const ANTI_MERIDIAN_THRESHOLD: f64 = 150.0;
53 longitude.abs() > ANTI_MERIDIAN_THRESHOLD
54 }
55}
56
57#[cfg(feature = "defmt")]
58impl defmt::Format for Location {
59 fn format(&self, fmt: defmt::Formatter) {
60 defmt::write!(
61 fmt,
62 "Location {{ latitude: {}, longitude: {}, elevation: {}, has_timezone: {} }}",
63 self.latitude,
64 self.longitude,
65 self.elevation,
66 self.timezone.is_some(),
67 )
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use jiff::tz::TimeZone;
75
76 #[test]
77 fn test_location_rejects_anti_meridian_without_timezone() {
78 let location = Location::new(0.0, 150.1, 0.0, None);
79 assert!(location.is_err());
80 }
81
82 #[test]
83 fn test_location_rejects_out_of_range_coords() {
84 let bad_longitude = Location::new(0.0, 181.0, 0.0, Some(TimeZone::UTC));
85 assert!(bad_longitude.is_err());
86
87 let bad_latitude = Location::new(91.0, 0.0, 0.0, Some(TimeZone::UTC));
88 assert!(bad_latitude.is_err());
89 }
90
91 #[test]
92 fn test_location_rejects_negative_elevation() {
93 let location = Location::new(0.0, 0.0, -1.0, Some(TimeZone::UTC));
94 assert!(location.is_err());
95 }
96}