use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::ops::{Add, Mul, Sub};
use std::str::FromStr;
use std::sync::OnceLock;
use crate::{lazy, TimeZone};
lazy!{
pub static ref COMMON_LOCATIONS: Vec<Location> = Location::common_locations().to_vec();
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Coordinate {
x: f32,
y: f32,
z: f32,
}
impl Default for Coordinate {
fn default() -> Self {
Self { x: 0.0, y: 0.0, z: 0.0 }
}
}
impl Coordinate {
pub fn new(x: impl Into<f32>, y: impl Into<f32>, z: impl Into<f32>) -> Self {
Self { x: x.into(), y: y.into(), z: z.into() }
}
pub fn x(&self) -> f32 {
self.x
}
pub fn y(&self) -> f32 {
self.y
}
pub fn z(&self) -> f32 {
self.z
}
pub fn distance(&self, other: &Self) -> f32 {
((self.x - other.x).powi(2) + (self.y - other.y).powi(2) + (self.z - other.z).powi(2)).sqrt()
}
}
impl fmt::Display for Coordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({:.2}, {:.2}, {:.2})", self.x, self.y, self.z)
}
}
impl Into<Coordinate> for (f32, f32, f32) {
fn into(self) -> Coordinate {
Coordinate::new(self.0, self.1, self.2)
}
}
impl Into<Coordinate> for (f32, f32) {
fn into(self) -> Coordinate {
Coordinate::new(self.0, self.1, 0.0)
}
}
impl Add for Coordinate {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
z: self.z + other.z,
}
}
}
impl Sub for Coordinate {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
z: self.z - other.z,
}
}
}
impl Mul<f32> for Coordinate {
type Output = Self;
fn mul(self, scalar: f32) -> Self {
Self {
x: self.x * scalar,
y: self.y * scalar,
z: self.z * scalar,
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct Location {
pub name: String,
pub coordinate: Coordinate,
pub utc_offset: i32,
}
impl Location {
pub fn new(name: impl Into<String>, coordinate: impl Into<Coordinate>, utc_offset: impl Into<i32>) -> Self {
Self {
name: name.into(),
coordinate: coordinate.into(),
utc_offset: utc_offset.into(),
}
}
pub fn timezone_offset(&self) -> i32 {
self.utc_offset
}
pub fn timezone(&self) -> TimeZone {
TimeZone::new(self.timezone_offset() as f32)
}
pub fn name(&self) -> &str {
&self.name
}
pub fn coordinate(&self) -> &Coordinate {
&self.coordinate
}
pub fn utc_offset(&self) -> i32 {
self.utc_offset
}
pub fn longitude(&self) -> f32 {
self.coordinate.y()
}
pub fn latitude(&self) -> f32 {
self.coordinate.x()
}
pub fn utc() -> Self {
Self::new("UTC" , Coordinate::new(0.0, 0.0, 0.0), 0)
}
pub fn kasaragod() -> Self {
Self::new("Kasaragod" , (12.5, 75.0), 330)
}
pub fn kannur() -> Self {
Self::new("Kannur" , (11.9, 75.4) , 330)
}
pub fn thalassery() -> Self {
Self::new("Thalassery",( 11.8, 75.5), 330)
}
pub fn mahe() -> Self {
Self::new("Mahe",( 11.7, 75.5), 330)
}
pub fn kuthuparamba() -> Self {
Self::new("Kuthuparamba",( 11.8, 75.6), 330)
}
pub fn kozhikode() -> Self {
Self::new("Kozhikode",( 11.2, 75.7), 330)
}
pub fn calicut() -> Self {
Self::kozhikode() }
pub fn vadakara() -> Self {
Self::new("Vadakara",( 11.6, 75.6), 330)
}
pub fn wayanad() -> Self {
Self::new("Wayanad",( 11.6, 76.1), 330)
}
pub fn malappuram() -> Self {
Self::new("Malappuram",( 10.7, 76.0), 330)
}
pub fn manjeri() -> Self {
Self::new("Manjeri",( 11.1, 76.1), 330)
}
pub fn perinthalmanna() -> Self {
Self::new("Perinthalmanna",( 10.9, 76.2), 330)
}
pub fn thrissur() -> Self {
Self::new("Thrissur",( 10.5, 76.2), 330)
}
pub fn kunnamkulam() -> Self {
Self::new("Kunnamkulam",( 10.7, 76.1), 330)
}
pub fn chalakudy() -> Self {
Self::new("Chalakudy",( 10.3, 76.3), 330)
}
pub fn palakkad() -> Self {
Self::new("Palakkad",( 10.7, 76.6), 330)
}
pub fn ottapalam() -> Self {
Self::new("Ottapalam",( 10.8, 76.4), 330)
}
pub fn shoranur() -> Self {
Self::new("Shoranur",( 10.8, 76.3), 330)
}
pub fn idukki() -> Self {
Self::new("Idukki",( 10.0, 76.8), 330)
}
pub fn munnar() -> Self {
Self::new("Munnar",( 10.1, 77.0), 330)
}
pub fn thekkady() -> Self {
Self::new("Thekkady",( 9.6, 77.2), 330)
}
pub fn kochi() -> Self {
Self::new("Kochi",( 9.9, 76.3), 330)
}
pub fn alappuzha() -> Self {
Self::new("Alappuzha",( 9.5, 76.3), 330)
}
pub fn kottayam() -> Self {
Self::new("Kottayam",( 9.6, 76.5), 330)
}
pub fn pathanamthitta() -> Self {
Self::new("Pathanamthitta",( 9.3, 76.8), 330)
}
pub fn kollam() -> Self {
Self::new("Kollam",( 8.9, 76.6), 330)
}
pub fn thiruvananthapuram() -> Self {
Self::new("Thiruvananthapuram",( 8.5, 76.9), 330)
}
pub fn varkala() -> Self {
Self::new("Varkala",( 8.7, 76.7), 330)
}
pub fn kanyakumari() -> Self {
Self::new("Kanyakumari",( 8.1, 77.5), 330)
}
pub fn dubai() -> Self {
Self::new("Dubai",( 25.2, 55.3), 240)
}
pub fn abu_dhabi() -> Self {
Self::new("Abu Dhabi",( 24.5, 54.4), 240)
}
pub fn sharjah() -> Self {
Self::new("Sharjah",( 25.3, 55.4), 240)
}
pub fn ajman() -> Self {
Self::new("Ajman",( 25.4, 55.5), 240)
}
pub fn fujairah() -> Self {
Self::new("Fujairah",( 25.1, 56.3), 240)
}
pub fn ras_al_khaimah() -> Self {
Self::new("Ras Al Khaimah",( 25.8, 55.9), 240)
}
pub fn umm_al_quwain() -> Self {
Self::new("Umm Al Quwain",( 25.6, 55.6), 240)
}
pub fn new_york() -> Self {
Self::new("New York",( 40.7, -74.0), -240)
}
pub fn london() -> Self {
Self::new("London",( 51.5, -0.1), 60)
}
pub fn tokyo() -> Self {
Self::new("Tokyo",( 35.7, 139.7), 540)
}
pub fn sydney() -> Self {
Self::new("Sydney",( -33.9, 151.2), 600)
}
pub fn paris() -> Self {
Self::new("Paris",( 48.9, 2.4), 120)
}
pub fn berlin() -> Self {
Self::new("Berlin",( 52.5, 13.4), 120)
}
pub fn moscow() -> Self {
Self::new("Moscow",( 55.8, 37.6), 180)
}
pub fn beijing() -> Self {
Self::new("Beijing",( 39.9, 116.4), 480)
}
pub fn mumbai() -> Self {
Self::new("Mumbai",( 19.1, 72.9), 330)
}
pub fn los_angeles() -> Self {
Self::new("Los Angeles",( 34.1, -118.2), -420)
}
pub fn chicago() -> Self {
Self::new("Chicago",( 41.9, -87.6), -300)
}
pub fn hong_kong() -> Self {
Self::new("Hong Kong",( 22.3, 114.2), 480)
}
pub fn common_locations() -> &'static [Location] {
static LOCATIONS: OnceLock<Vec<Location>> = OnceLock::new();
LOCATIONS.get_or_init(|| {
vec![
Self::utc(),
Self::kasaragod(),
Self::kannur(),
Self::thalassery(),
Self::mahe(),
Self::kuthuparamba(),
Self::kozhikode(),
Self::vadakara(),
Self::wayanad(),
Self::malappuram(),
Self::manjeri(),
Self::perinthalmanna(),
Self::thrissur(),
Self::kunnamkulam(),
Self::chalakudy(),
Self::palakkad(),
Self::ottapalam(),
Self::shoranur(),
Self::idukki(),
Self::munnar(),
Self::thekkady(),
Self::kochi(),
Self::alappuzha(),
Self::kottayam(),
Self::pathanamthitta(),
Self::kollam(),
Self::thiruvananthapuram(),
Self::varkala(),
Self::kanyakumari(),
Self::dubai(),
Self::abu_dhabi(),
Self::sharjah(),
Self::ajman(),
Self::fujairah(),
Self::ras_al_khaimah(),
Self::umm_al_quwain(),
Self::new_york(),
Self::london(),
Self::tokyo(),
Self::sydney(),
Self::paris(),
Self::berlin(),
Self::moscow(),
Self::beijing(),
Self::mumbai(),
Self::los_angeles(),
Self::chicago(),
Self::hong_kong(),
]
})
}
pub fn find(name: &str) -> Option<&'static Location> {
Self::common_locations()
.iter()
.find(|loc| loc.name.eq_ignore_ascii_case(name))
}
}
impl FromStr for Location {
type Err = Box<dyn Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Location::find(s)
.cloned()
.ok_or_else(|| format!("Location not found: {}", s).into())
}
}
impl Display for Location {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"{} (Lat: {:.2}, Lon: {:.2}, UTC Offset: {:+})",
self.name,
self.latitude(),
self.longitude(),
self.utc_offset
)
}
}
impl Default for Location {
fn default() -> Self {
Location{
name: "".to_string(),
coordinate: Coordinate::default(),
utc_offset: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_location_creation() {
let loc = Location::new("Test City".to_string(), (10.0, 20.0), 180);
assert_eq!(loc.name(), "Test City");
assert_eq!(loc.latitude(), 10.0);
assert_eq!(loc.longitude(), 20.0);
assert_eq!(loc.utc_offset(), 180);
}
#[test]
fn test_predefined_locations() {
let nyc = Location::new_york();
assert_eq!(nyc.name(), "New York");
assert_eq!(nyc.latitude(), 40.7);
assert_eq!(nyc.longitude(), -74.0);
assert_eq!(nyc.utc_offset(), -240);
let tokyo = Location::tokyo();
assert_eq!(tokyo.name(), "Tokyo");
assert_eq!(tokyo.latitude(), 35.7);
assert_eq!(tokyo.longitude(), 139.7);
assert_eq!(tokyo.utc_offset(), 540);
}
#[test]
fn test_location_from_str() {
let dubai: Location = "Dubai".parse().unwrap();
assert_eq!(dubai, Location::dubai());
let result: Result<Location, _> = "NonexistentCity".parse();
assert!(result.is_err());
}
#[test]
fn test_location_find() {
let london = Location::find("London");
assert!(london.is_some());
assert_eq!(london.unwrap().name(), "London");
let nonexistent = Location::find("NonexistentCity");
assert!(nonexistent.is_none());
}
#[test]
fn test_common_locations() {
let locations = Location::common_locations();
assert!(locations.len() > 0);
assert!(locations.iter().any(|loc| loc.name() == "Dubai"));
assert!(locations.iter().any(|loc| loc.name() == "New York"));
}
}