use std::ops::{Add, Sub};
use jiff::{SignedDuration, Span, Zoned, civil::Date, tz::TimeZone};
use crate::util::geolocation::GeoLocation;
use crate::util::math_helper::HOUR_NANOS;
use crate::util::noaa_calculator;
pub const GEOMETRIC_ZENITH: f64 = 90.0;
pub const CIVIL_ZENITH: f64 = 96.0;
pub const NAUTICAL_ZENITH: f64 = 102.0;
pub const ASTRONOMICAL_ZENITH: f64 = 108.0;
#[must_use]
pub fn utc_sunrise(date: &Date, zenith: f64, geo_location: &GeoLocation) -> Option<f64> {
noaa_calculator::utc_sunrise(date, geo_location, zenith, true)
}
#[must_use]
pub fn utc_sunset(date: &Date, zenith: f64, geo_location: &GeoLocation) -> Option<f64> {
noaa_calculator::utc_sunset(date, geo_location, zenith, true)
}
#[must_use]
pub fn utc_sea_level_sunrise(date: &Date, zenith: f64, geo_location: &GeoLocation) -> Option<f64> {
noaa_calculator::utc_sunrise(date, geo_location, zenith, false)
}
#[must_use]
pub fn utc_sea_level_sunset(date: &Date, zenith: f64, geo_location: &GeoLocation) -> Option<f64> {
noaa_calculator::utc_sunset(date, geo_location, zenith, false)
}
#[must_use]
pub fn sunrise(date: &Date, geo_location: &GeoLocation) -> Option<Zoned> {
Some(date_time_from_time_of_day(
date,
noaa_calculator::utc_sunrise(date, geo_location, GEOMETRIC_ZENITH, true)?,
&geo_location.timezone,
))
}
#[must_use]
pub fn sunset(date: &Date, geo_location: &GeoLocation) -> Option<Zoned> {
Some(date_time_from_time_of_day(
date,
noaa_calculator::utc_sunset(date, geo_location, GEOMETRIC_ZENITH, true)?,
&geo_location.timezone,
))
}
#[must_use]
pub fn sea_level_sunrise(date: &Date, geo_location: &GeoLocation) -> Option<Zoned> {
sunrise_offset_by_degrees(date, geo_location, GEOMETRIC_ZENITH)
}
#[must_use]
pub fn sea_level_sunset(date: &Date, geo_location: &GeoLocation) -> Option<Zoned> {
sunset_offset_by_degrees(date, geo_location, GEOMETRIC_ZENITH)
}
#[must_use]
pub fn sunrise_offset_by_degrees(
date: &Date,
geo_location: &GeoLocation,
offset_zenith: f64,
) -> Option<Zoned> {
Some(date_time_from_time_of_day(
date,
utc_sea_level_sunrise(date, offset_zenith, geo_location)?,
&geo_location.timezone,
))
}
#[must_use]
pub fn sunset_offset_by_degrees(
date: &Date,
geo_location: &GeoLocation,
offset_zenith: f64,
) -> Option<Zoned> {
Some(date_time_from_time_of_day(
date,
utc_sea_level_sunset(date, offset_zenith, geo_location)?,
&geo_location.timezone,
))
}
#[must_use]
pub fn temporal_hour(sunrise: &Zoned, sunset: &Zoned) -> SignedDuration {
sunset.duration_since(sunrise) / 12
}
#[must_use]
pub fn solar_noon(date: &Date, geo_location: &GeoLocation) -> Option<Zoned> {
Some(date_time_from_time_of_day(
date,
noaa_calculator::utc_noon(date, geo_location)?,
&geo_location.timezone,
))
}
#[must_use]
pub fn solar_midnight(date: &Date, geo_location: &GeoLocation) -> Option<Zoned> {
let midnight = date_time_from_time_of_day(
date,
noaa_calculator::utc_midnight(date, geo_location)?,
&geo_location.timezone,
);
let noon_hour = date
.to_zoned(geo_location.timezone.clone())
.unwrap()
.with()
.hour(12)
.build()
.unwrap();
if midnight <= noon_hour {
Some(midnight.add(Span::new().days(1)))
} else {
Some(midnight)
}
}
fn date_time_from_time_of_day(date: &Date, time_of_day: f64, timezone: &TimeZone) -> Zoned {
let total_nanos = (time_of_day * HOUR_NANOS).round() as i64;
let utc_dt = date
.to_zoned(TimeZone::UTC)
.unwrap()
.add(SignedDuration::from_nanos(total_nanos));
let local_dt = utc_dt.with_time_zone(timezone.clone());
if local_dt.date() < *date {
local_dt.add(Span::new().days(1))
} else if local_dt.date() > *date {
local_dt.sub(Span::new().days(1))
} else {
local_dt
}
}
#[must_use]
pub fn local_mean_time(date: &Date, geo_location: &GeoLocation, hours: f64) -> Option<Zoned> {
if (0.0..24.0).contains(&hours) {
let time_of_day = hours - geo_location.local_mean_time_offset();
Some(date_time_from_time_of_day(
date,
time_of_day,
&geo_location.timezone,
))
} else {
None
}
}