use std::{f64::consts::TAU, time::SystemTime};
const MOON_SYNODIC_PERIOD: f64 = 29.530588853; const MOON_SYNODIC_OFFSET: f64 = 2451550.26; const MOON_DISTANCE_PERIOD: f64 = 27.55454988; const MOON_DISTANCE_OFFSET: f64 = 2451562.2;
const MOON_LATITUDE_PERIOD: f64 = 27.212220817; const MOON_LATITUDE_OFFSET: f64 = 2451565.2;
const MOON_LONGITUDE_PERIOD: f64 = 27.321582241; const MOON_LONGITUDE_OFFSET: f64 = 2451555.8;
const PHASE_NAMES: &[&str] = &[
"New",
"Waxing Crescent",
"First Quarter",
"Waxing Gibbous",
"Full",
"Waning Gibbous",
"Last Quarter",
"Waning Crescent",
];
const ZODIAC_NAMES: [&str; 12] = [
"Pisces",
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
];
const ZODIAC_ANGLES: [f64; 12] = [
33.18, 51.16, 93.44, 119.48, 135.30, 173.34, 224.17, 242.57, 271.26,
302.49, 311.72, 348.58,
];
#[derive(Debug, Copy, Clone)]
pub struct MoonPhase {
pub j_date: f64,
pub phase: f64, pub age: f64, pub fraction: f64, pub distance: f64, pub latitude: f64, pub longitude: f64, pub phase_name: &'static str, pub zodiac_name: &'static str, }
fn julian_date(time: SystemTime) -> f64 {
let secs = match time.duration_since(SystemTime::UNIX_EPOCH) {
Ok(duration) => duration.as_secs_f64(),
Err(earlier) => -1. * earlier.duration().as_secs_f64(),
};
secs / 86400. + 2440587.5
}
impl MoonPhase {
pub fn new(time: SystemTime) -> Self {
let j_date = julian_date(time);
let phase =
((j_date - MOON_SYNODIC_OFFSET) / MOON_SYNODIC_PERIOD).fract();
let age = phase * MOON_SYNODIC_PERIOD;
let fraction = (1. - (std::f64::consts::TAU * phase)).cos() / 2.;
let phase_name = PHASE_NAMES[(phase * 8.).round() as usize % 8];
let distance_phase =
((j_date - MOON_DISTANCE_OFFSET) / MOON_DISTANCE_PERIOD).fract();
let distance_phase_tau = TAU * distance_phase;
let phase_tau = 2. * TAU * phase;
let phase_distance_tau_difference = phase_tau - distance_phase_tau;
let distance = 60.4
- 3.3 * distance_phase_tau.cos()
- 0.6 * (phase_distance_tau_difference).cos()
- 0.5 * (phase_tau).cos();
let lat_phase =
((j_date - MOON_LATITUDE_OFFSET) / MOON_LATITUDE_PERIOD).fract();
let latitude = 5.1 * (TAU * lat_phase).sin();
let long_phase =
((j_date - MOON_LONGITUDE_OFFSET) / MOON_LONGITUDE_PERIOD).fract();
let longitude = (360. * long_phase
+ 6.3 * (distance_phase_tau).sin()
+ 1.3 * (phase_distance_tau_difference).sin()
+ 0.7 * (phase_tau).sin())
% 360.;
let zodiac_name = ZODIAC_ANGLES
.iter()
.zip(ZODIAC_NAMES.iter())
.find_map(|(angle, name)| {
if longitude < *angle {
Some(*name)
} else {
None
}
})
.unwrap_or_else(|| ZODIAC_NAMES[0]);
MoonPhase {
j_date,
phase,
age,
fraction,
distance,
latitude,
longitude,
phase_name,
zodiac_name,
}
}
}