use std::ops::{Add, Sub};
use jiff::{SignedDuration, Zoned, civil::Date};
use crate::{
astronomical_calculator,
util::{geolocation::GeoLocation, math_helper::MINUTE_NANOS},
};
#[must_use]
pub fn alos(
date: Date,
geo_location: &GeoLocation,
use_elevation: bool,
offset: &ZmanOffset,
) -> Option<Zoned> {
match offset {
ZmanOffset::Degrees(deg) => astronomical_calculator::sunrise_offset_by_degrees(
date,
geo_location,
astronomical_calculator::GEOMETRIC_ZENITH + deg,
),
_ => Some(offset_before_event(
&hanetz(date, geo_location, use_elevation)?,
offset,
)),
}
}
pub(crate) fn offset_before_event(event: &Zoned, offset: &ZmanOffset) -> Zoned {
match offset {
ZmanOffset::Degrees(_) => unreachable!("degree offsets are not relative to an event"),
ZmanOffset::Minutes(min) => {
event.sub(SignedDuration::from_nanos((min * MINUTE_NANOS) as i64))
}
ZmanOffset::MinutesZmaniyos {
minutes_zmaniyos: minz,
shaah_zmanis: shaah,
} => offset_by_minutes_zmanis(event, -minz, *shaah),
}
}
pub(crate) fn offset_after_event(event: &Zoned, offset: &ZmanOffset) -> Zoned {
match offset {
ZmanOffset::Degrees(_) => unreachable!("degree offsets are not relative to an event"),
ZmanOffset::Minutes(min) => {
event.add(SignedDuration::from_nanos((min * MINUTE_NANOS) as i64))
}
ZmanOffset::MinutesZmaniyos {
minutes_zmaniyos: minz,
shaah_zmanis: shaah,
} => offset_by_minutes_zmanis(event, *minz, *shaah),
}
}
#[must_use]
pub fn hanetz(date: Date, geo_location: &GeoLocation, use_elevation: bool) -> Option<Zoned> {
if use_elevation {
astronomical_calculator::sunrise(date, geo_location)
} else {
astronomical_calculator::sea_level_sunrise(date, geo_location)
}
}
#[must_use]
pub fn sof_zman_shema(day_start: &Zoned, day_end: &Zoned) -> Zoned {
shaos_into_day(day_start, day_end, 3.0)
}
#[must_use]
pub fn sof_zman_tefila(day_start: &Zoned, day_end: &Zoned) -> Zoned {
shaos_into_day(day_start, day_end, 4.0)
}
#[must_use]
pub fn sof_zman_biur_chametz(day_start: &Zoned, day_end: &Zoned) -> Zoned {
shaos_into_day(day_start, day_end, 5.0)
}
#[must_use]
pub fn chatzos_hayom(date: Date, geo_location: &GeoLocation) -> Option<Zoned> {
astronomical_calculator::solar_noon(date, geo_location)
}
#[must_use]
pub fn chatzos_halayla(date: Date, geo_location: &GeoLocation) -> Option<Zoned> {
astronomical_calculator::solar_midnight(date, geo_location)
}
#[must_use]
pub fn fixed_local_chatzos_hayom(date: Date, geo_location: &GeoLocation) -> Option<Zoned> {
astronomical_calculator::local_mean_time(date, geo_location, 12.0)
}
#[must_use]
pub fn chatzos_hayom_as_half_day(day_start: &Zoned, day_end: &Zoned) -> Zoned {
day_start.add(day_end.duration_since(day_start) / 2)
}
#[must_use]
pub fn mincha_gedola_30_minutes(date: Date, geo_location: &GeoLocation) -> Option<Zoned> {
Some(chatzos_hayom(date, geo_location)?.add(SignedDuration::from_mins(30)))
}
#[must_use]
pub fn mincha_gedola(day_start: &Zoned, day_end: &Zoned) -> Zoned {
shaos_into_day(day_start, day_end, 6.5)
}
#[must_use]
pub fn samuch_lemincha_ketana(day_start: &Zoned, day_end: &Zoned) -> Zoned {
shaos_into_day(day_start, day_end, 9.0)
}
#[must_use]
pub fn mincha_ketana(day_start: &Zoned, day_end: &Zoned) -> Zoned {
shaos_into_day(day_start, day_end, 9.5)
}
#[must_use]
pub fn plag_hamincha(day_start: &Zoned, day_end: &Zoned) -> Zoned {
shaos_into_day(day_start, day_end, 10.75)
}
#[must_use]
pub fn shkia(date: Date, geo_location: &GeoLocation, use_elevation: bool) -> Option<Zoned> {
if use_elevation {
astronomical_calculator::sunset(date, geo_location)
} else {
astronomical_calculator::sea_level_sunset(date, geo_location)
}
}
#[must_use]
pub fn tzeis(
date: Date,
geo_location: &GeoLocation,
use_elevation: bool,
offset: &ZmanOffset,
) -> Option<Zoned> {
match offset {
ZmanOffset::Degrees(deg) => astronomical_calculator::sunset_offset_by_degrees(
date,
geo_location,
astronomical_calculator::GEOMETRIC_ZENITH + deg,
),
_ => Some(offset_after_event(
&shkia(date, geo_location, use_elevation)?,
offset,
)),
}
}
#[must_use]
pub fn shaah_zmanis(day_start: &Zoned, day_end: &Zoned) -> SignedDuration {
astronomical_calculator::temporal_hour(day_start, day_end)
}
fn offset_by_minutes_zmanis(time: &Zoned, minutes: f64, shaah_zmanis: SignedDuration) -> Zoned {
let total_nanos = (shaah_zmanis.as_nanos() as f64 * (minutes / 60.0)) as i64;
time.add(SignedDuration::from_nanos(total_nanos))
}
fn shaos_into_day(day_start: &Zoned, day_end: &Zoned, shaos: f64) -> Zoned {
let shaah_zmanis = astronomical_calculator::temporal_hour(day_start, day_end);
offset_by_minutes_zmanis(day_start, shaos * 60.0, shaah_zmanis)
}
#[must_use]
pub fn half_day_based_shaah_zmanis(
start_of_half_day: &Zoned,
end_of_half_day: &Zoned,
) -> SignedDuration {
end_of_half_day.duration_since(start_of_half_day) / 6
}
#[must_use]
pub fn half_day_based_zman(
start_of_half_day: &Zoned,
end_of_half_day: &Zoned,
hours: f64,
) -> Zoned {
let shaah_zmanis = half_day_based_shaah_zmanis(start_of_half_day, end_of_half_day);
let offset = SignedDuration::from_nanos((shaah_zmanis.as_nanos() as f64 * hours) as i64);
if hours >= 0.0 {
start_of_half_day.add(offset)
} else {
end_of_half_day.add(offset)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ZmanOffset {
Degrees(f64),
Minutes(f64),
MinutesZmaniyos {
minutes_zmaniyos: f64,
shaah_zmanis: SignedDuration,
},
}