1use std::{f64::consts::TAU, time::SystemTime};
2
3const 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;
7const MOON_LATITUDE_PERIOD: f64 = 27.212220817; const MOON_LATITUDE_OFFSET: f64 = 2451565.2;
9const MOON_LONGITUDE_PERIOD: f64 = 27.321582241; const MOON_LONGITUDE_OFFSET: f64 = 2451555.8;
11
12const PHASE_NAMES: &[&str] = &[
14 "New",
15 "Waxing Crescent",
16 "First Quarter",
17 "Waxing Gibbous",
18 "Full",
19 "Waning Gibbous",
20 "Last Quarter",
21 "Waning Crescent",
22];
23const ZODIAC_NAMES: [&str; 12] = [
25 "Pisces",
26 "Aries",
27 "Taurus",
28 "Gemini",
29 "Cancer",
30 "Leo",
31 "Virgo",
32 "Libra",
33 "Scorpio",
34 "Sagittarius",
35 "Capricorn",
36 "Aquarius",
37];
38const ZODIAC_ANGLES: [f64; 12] = [
40 33.18, 51.16, 93.44, 119.48, 135.30, 173.34, 224.17, 242.57, 271.26,
41 302.49, 311.72, 348.58,
42];
43
44#[derive(Debug, Copy, Clone)]
45pub struct MoonPhase {
46 pub j_date: f64,
47 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, }
56
57fn julian_date(time: SystemTime) -> f64 {
58 let secs = match time.duration_since(SystemTime::UNIX_EPOCH) {
59 Ok(duration) => duration.as_secs_f64(),
60 Err(earlier) => -1. * earlier.duration().as_secs_f64(),
61 };
62 secs / 86400. + 2440587.5
63}
64
65impl MoonPhase {
66 pub fn new(time: SystemTime) -> Self {
67 let j_date = julian_date(time);
68
69 let phase =
73 ((j_date - MOON_SYNODIC_OFFSET) / MOON_SYNODIC_PERIOD).fract();
74 let age = phase * MOON_SYNODIC_PERIOD;
76 let fraction = (1. - (std::f64::consts::TAU * phase)).cos() / 2.;
77 let phase_name = PHASE_NAMES[(phase * 8.).round() as usize % 8];
78 let distance_phase =
80 ((j_date - MOON_DISTANCE_OFFSET) / MOON_DISTANCE_PERIOD).fract();
81 let distance_phase_tau = TAU * distance_phase;
82 let phase_tau = 2. * TAU * phase;
83 let phase_distance_tau_difference = phase_tau - distance_phase_tau;
84 let distance = 60.4
85 - 3.3 * distance_phase_tau.cos()
86 - 0.6 * (phase_distance_tau_difference).cos()
87 - 0.5 * (phase_tau).cos();
88
89 let lat_phase =
91 ((j_date - MOON_LATITUDE_OFFSET) / MOON_LATITUDE_PERIOD).fract();
92 let latitude = 5.1 * (TAU * lat_phase).sin();
93
94 let long_phase =
96 ((j_date - MOON_LONGITUDE_OFFSET) / MOON_LONGITUDE_PERIOD).fract();
97 let longitude = (360. * long_phase
98 + 6.3 * (distance_phase_tau).sin()
99 + 1.3 * (phase_distance_tau_difference).sin()
100 + 0.7 * (phase_tau).sin())
101 % 360.;
102
103 let zodiac_name = ZODIAC_ANGLES
104 .iter()
105 .zip(ZODIAC_NAMES.iter())
106 .find_map(|(angle, name)| {
107 if longitude < *angle {
108 Some(*name)
109 } else {
110 None
111 }
112 })
113 .unwrap_or_else(|| ZODIAC_NAMES[0]);
114 MoonPhase {
115 j_date,
116 phase,
117 age,
118 fraction,
119 distance,
120 latitude,
121 longitude,
122 phase_name,
123 zodiac_name,
124 }
125 }
126}