deep_time/sidereal/mod.rs
1//! Sidereal rotation and time calculations for celestial bodies.
2//!
3//! [`Sidereal`] struct with ready-to-use `EARTH`, `MARS`, `MOON` constants.
4//! Computes rotation angle, LMST/LAST, GMST/GAST.
5//!
6//! With the `"sidereal-earth"` feature enabled a rust implementation of the
7//! ERFA Earth Equation of the Origins / Equinoxes are both available as well.
8
9/// ERFA Earth equation of the origins / equinoxes (`sidereal-earth` feature).
10#[cfg(feature = "sidereal-earth")]
11pub mod earth_eo_ee;
12
13use crate::Real;
14use core::f64::consts::TAU;
15
16#[cfg(feature = "sidereal-earth")]
17use earth_eo_ee::*;
18
19/// Represents the rotational state of a celestial body and provides
20/// methods to compute the orientation of its prime meridian at any
21/// given time.
22///
23/// The rotation angle of the prime meridian is the basis for
24/// calculating local sidereal time. Local sidereal time is required
25/// to compute the hour angle of a celestial object (HA = LST − RA),
26/// to determine when an object will cross the local meridian,
27/// to convert between horizon coordinates (altitude/azimuth) and
28/// equatorial coordinates, and to calculate accurate pointing
29/// directions for telescopes and spacecraft antennas.
30///
31/// The struct implements the modern CIO-based rotation model and
32/// works for any rotating body (Earth, Mars, the Moon, etc.) by
33/// supplying the appropriate rotation rate and reference values.
34///
35/// ## Fields
36///
37/// * `rate_rad_per_sec` — Mean sidereal rotation rate in radians per SI second.
38/// * `ref_epoch` — Reference epoch (MJD) at which `ref_angle_rad` is defined.
39/// * `ref_angle_rad` — Rotation angle of the prime meridian at `ref_epoch`.
40/// * `longitude_rad` — Observer longitude on the body (radians, east positive).
41/// `0.0` corresponds to the body's prime meridian.
42/// * `correction_rad` — General-purpose additive correction in radians.
43///
44/// ## Examples
45///
46/// Basic usage with Earth constants:
47///
48/// ```rust
49/// use deep_time::Sidereal;
50///
51/// let mut earth = Sidereal::EARTH;
52/// earth.longitude_rad = 0.0; // Greenwich
53///
54/// let mjd = 60000.0;
55/// let era = earth.rotation_angle(mjd);
56///
57/// // Local Mean Sidereal Time using the mean Equation of the Origins
58/// // (requires the "sidereal-earth" feature)
59/// # #[cfg(feature = "sidereal-earth")] {
60/// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
61/// let lmst = earth.local_sidereal_time_mean(mjd, eo_mean);
62/// # }
63/// ```
64///
65/// Realistic usage with DUT1 correction (UT1 time scale):
66///
67/// ```rust
68/// // This advanced example requires the "eop" feature for EopData
69/// // and "sidereal-earth" for the EO calculations.
70/// # #[cfg(all(feature = "eop", feature = "sidereal-earth"))] {
71/// use deep_time::Dt;
72/// use deep_time::Sidereal;
73/// use deep_time::eop::{EopData, EopFormat, Separator};
74///
75/// let eop = EopData::from_text_file(
76/// "tests/assets/finals.all.iau2000.txt",
77/// EopFormat::Finals2000A,
78/// Separator::Whitespace,
79/// ).unwrap();
80///
81/// let mjd_utc = 56879.0;
82/// let dut1 = Dt::mjd_to_eop_offset_f(mjd_utc, &eop).unwrap();
83/// let mjd_ut1 = mjd_utc + dut1 / 86400.0;
84///
85/// let earth = Sidereal::EARTH;
86///
87/// let era = earth.rotation_angle(mjd_ut1);
88///
89/// let eo_mean = earth.earth_eo_mean(mjd_ut1 + 32.184 / 86400.0);
90/// let gmst = earth.sidereal_angle_mean(mjd_ut1, eo_mean);
91///
92/// // Local Mean Sidereal Time
93/// let lmst = earth.local_sidereal_time_mean(mjd_ut1, eo_mean);
94/// # }
95/// ```
96#[derive(Clone, Debug, PartialEq)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98#[cfg_attr(feature = "tsify", derive(tsify::Tsify))]
99#[cfg_attr(feature = "defmt", derive(defmt::Format))]
100pub struct Sidereal {
101 /// Mean sidereal rotation rate in **radians per SI second**.
102 pub rate_rad_per_sec: Real,
103 /// Reference epoch.
104 pub ref_epoch: Real,
105 /// Rotation angle of the prime meridian (radians) at `ref_epoch`.
106 pub ref_angle_rad: Real,
107 /// Longitude of the observer on the body (radians, east positive).
108 /// `0.0` = body's prime meridian.
109 pub longitude_rad: Real,
110 /// General scalar correction in radians.
111 pub correction_rad: Real,
112}
113
114impl Sidereal {
115 /// Pre-configured `Sidereal` for Earth using IAU 2000/2006 conventions.
116 ///
117 /// This uses:
118 /// - The conventional mean sidereal rotation rate of Earth.
119 /// - J2000.0 as the reference epoch (`ref_epoch = 51544.5`).
120 /// - The Earth Rotation Angle (ERA) at J2000.0 as `ref_angle_rad`.
121 ///
122 /// You can still customize fields after construction (e.g. `longitude_rad`
123 /// or `correction_rad`).
124 pub const EARTH: Self = Self {
125 rate_rad_per_sec: (1.00273781191135448 * core::f64::consts::TAU) / 86400.0,
126 ref_epoch: 51544.5,
127 ref_angle_rad: 0.7790572732640 * core::f64::consts::TAU,
128 longitude_rad: 0.0,
129 correction_rad: 0.0,
130 };
131
132 /// Pre-configured `Sidereal` for Mars.
133 ///
134 /// Uses a simplified mean sidereal rotation rate and J2000.0 as the
135 /// reference epoch. `ref_angle_rad` is set to zero (no specific
136 /// reference angle is defined).
137 ///
138 /// You can customize fields (especially `longitude_rad`) after construction.
139 pub const MARS: Self = Self {
140 rate_rad_per_sec: core::f64::consts::TAU / 88642.663,
141 ref_epoch: 51544.5,
142 ref_angle_rad: 0.0,
143 longitude_rad: 0.0,
144 correction_rad: 0.0,
145 };
146
147 /// Pre-configured `Sidereal` for the Moon.
148 ///
149 /// Uses a simplified mean sidereal rotation rate and J2000.0 as the
150 /// reference epoch. `ref_angle_rad` is set to zero (no specific
151 /// reference angle is defined).
152 ///
153 /// You can customize fields (especially `longitude_rad`) after construction.
154 pub const MOON: Self = Self {
155 rate_rad_per_sec: core::f64::consts::TAU / 2_360_591.424,
156 ref_epoch: 51544.5,
157 ref_angle_rad: 0.0,
158 longitude_rad: 0.0,
159 correction_rad: 0.0,
160 };
161
162 // Normalize to [0, 2π)
163 #[inline]
164 const fn normalize_angle(angle: Real) -> Real {
165 ((angle % TAU) + TAU) % TAU
166 }
167
168 /// Returns the instantaneous rotation angle of the body's prime meridian
169 /// (in radians) at the given instant, normalized to `[0, 2π)`.
170 ///
171 /// For Earth this is the pure Earth Rotation Angle (ERA) in the
172 /// Celestial Intermediate Origin (CIO) frame. It does **not** include
173 /// observer longitude or the Equation of the Origins.
174 ///
175 /// Matches Astropy's `Time.earth_rotation_angle(longitude=None)`
176 /// (or with `longitude=0`).
177 ///
178 /// ## Examples
179 ///
180 /// ```rust
181 /// use deep_time::Sidereal;
182 ///
183 /// let era = Sidereal::EARTH.rotation_angle(57753.5);
184 /// ```
185 pub const fn rotation_angle(&self, mjd: Real) -> Real {
186 // elapsed time in seconds between ref_epoch (MJD) and the given mjd
187 let elapsed_days = mjd - self.ref_epoch;
188 let elapsed_sec = elapsed_days * 86400.0;
189
190 let angle = self.ref_angle_rad + self.rate_rad_per_sec * elapsed_sec + self.correction_rad;
191
192 Self::normalize_angle(angle)
193 }
194
195 /// Returns the rotation angle of the prime meridian at the observer's
196 /// longitude, normalized to `[0, 2π)`.
197 ///
198 /// This is equivalent to `rotation_angle(mjd) + self.longitude_rad`.
199 /// It gives the angle between the Celestial Intermediate Origin (CIO)
200 /// and the observer’s local meridian.
201 ///
202 /// This value is commonly used when computing the local hour angle
203 /// of a celestial object:
204 ///
205 /// ```text
206 /// HA = local_rotation_angle(mjd) - RA
207 /// ```
208 ///
209 /// ## Examples
210 ///
211 /// ```rust
212 /// use deep_time::Sidereal;
213 ///
214 /// let mut earth = Sidereal::EARTH;
215 /// earth.longitude_rad = 0.0; // Greenwich
216 ///
217 /// let mjd = 60000.0;
218 /// let local_era = earth.local_rotation_angle(mjd);
219 /// ```
220 #[inline]
221 pub const fn local_rotation_angle(&self, mjd: Real) -> Real {
222 Self::normalize_angle(self.rotation_angle(mjd) + self.longitude_rad)
223 }
224
225 /// Returns the sidereal angle of the body's prime meridian in radians,
226 /// normalized to `[0, 2π)`.
227 ///
228 /// This computes Greenwich Mean Sidereal Time (GMST) when an appropriate
229 /// Equation of the Origins value is supplied.
230 ///
231 /// ## Parameters
232 ///
233 /// - `eo_rad`: The Equation of the Origins value to subtract from the
234 /// Earth Rotation Angle (ERA).
235 /// - Pass `0.0` to get the pure CIO-based rotation angle (ERA).
236 /// - Pass the **mean** Equation of the Origins (e.g. from
237 /// [`Sidereal::earth_eo_mean`](#method.earth_eo_mean)) to obtain GMST.
238 ///
239 /// ## Details
240 ///
241 /// - When `eo_rad = 0.0`, the result is the modern Earth Rotation Angle (ERA)
242 /// relative to the Celestial Intermediate Origin (CIO).
243 ///
244 /// - When `eo_rad` is the mean Equation of the Origins (i.e. the value that
245 /// satisfies `GMST = ERA − eo_rad`), the result is Greenwich Mean Sidereal
246 /// Time (GMST) referred to the mean equinox. This is the traditional
247 /// equinox-based mean sidereal time.
248 ///
249 /// ## Examples
250 ///
251 /// ```rust
252 /// use deep_time::Sidereal;
253 ///
254 /// let earth = Sidereal::EARTH;
255 /// let mjd = 60000.0;
256 ///
257 /// // Pure CIO-based rotation angle (Earth Rotation Angle)
258 /// let era = earth.sidereal_angle_mean(mjd, 0.0);
259 ///
260 /// // Traditional mean sidereal time using the mean Equation of the Origins
261 /// // (requires "sidereal-earth" feature)
262 /// # #[cfg(feature = "sidereal-earth")] {
263 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
264 /// let gmst = earth.sidereal_angle_mean(mjd, eo_mean);
265 /// # }
266 /// ```
267 #[inline]
268 pub const fn sidereal_angle_mean(&self, mjd: Real, eo_rad: Real) -> Real {
269 let angle = self.rotation_angle(mjd) - eo_rad;
270 Self::normalize_angle(angle)
271 }
272
273 /// Returns the local sidereal angle at the observer's longitude in radians,
274 /// normalized to `[0, 2π)`.
275 ///
276 /// This computes **Local Mean Sidereal Time (LMST)** when an appropriate
277 /// Equation of the Origins value is supplied.
278 ///
279 /// ## Parameters
280 ///
281 /// - `eo_rad`: The Equation of the Origins value to subtract from the
282 /// Earth Rotation Angle (ERA).
283 /// - Pass `0.0` to get the pure local Earth Rotation Angle (CIO-based).
284 /// - Pass the **mean** Equation of the Origins (e.g. from
285 /// [`Sidereal::earth_eo_mean`](#method.earth_eo_mean)) to obtain Local Mean
286 /// Sidereal Time (LMST).
287 ///
288 /// ## Details
289 ///
290 /// - When `eo_rad = 0.0`, the result is the local Earth Rotation Angle
291 /// relative to the Celestial Intermediate Origin (CIO) at the observer’s
292 /// longitude.
293 ///
294 /// - When `eo_rad` is the mean Equation of the Origins, the result is
295 /// **Local Mean Sidereal Time (LMST)** referred to the mean equinox.
296 ///
297 /// This value is commonly used when calculating the local hour angle of a
298 /// celestial object:
299 ///
300 /// ```text
301 /// HA = local_sidereal_angle_mean(mjd, eo) − RA
302 /// ```
303 ///
304 /// ## Examples
305 ///
306 /// ```rust
307 /// use deep_time::Sidereal;
308 ///
309 /// let mut earth = Sidereal::EARTH;
310 /// earth.longitude_rad = 0.0; // Greenwich
311 ///
312 /// let mjd = 60000.0;
313 ///
314 /// // Pure local Earth Rotation Angle (CIO-based)
315 /// let local_era = earth.local_sidereal_angle_mean(mjd, 0.0);
316 ///
317 /// // Local Mean Sidereal Time using the mean Equation of the Origins
318 /// // (requires "sidereal-earth" feature)
319 /// # #[cfg(feature = "sidereal-earth")] {
320 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
321 /// let lmst = earth.local_sidereal_angle_mean(mjd, eo_mean);
322 /// # }
323 /// ```
324 #[inline]
325 pub const fn local_sidereal_angle_mean(&self, mjd: Real, eo_rad: Real) -> Real {
326 let angle = self.rotation_angle(mjd) + self.longitude_rad - eo_rad;
327 Self::normalize_angle(angle)
328 }
329
330 /// Returns sidereal time at the body's prime meridian as seconds since
331 /// sidereal midnight, wrapped to the range `[0, 86400)`.
332 ///
333 /// This is the time equivalent of
334 /// [`Sidereal::sidereal_angle_mean`].
335 ///
336 /// ## Parameters
337 ///
338 /// - `eo_rad`: The Equation of the Origins value to use.
339 /// - Pass `0.0` to get the time equivalent of the pure Earth Rotation Angle (ERA).
340 /// - Pass the **mean** Equation of the Origins (e.g. from
341 /// [`Sidereal::earth_eo_mean`](#method.earth_eo_mean)) to obtain Greenwich Mean
342 /// Sidereal Time (GMST).
343 ///
344 /// ## Details
345 ///
346 /// - When `eo_rad = 0.0`, the result is the time equivalent of the modern
347 /// Earth Rotation Angle (ERA).
348 ///
349 /// - When `eo_rad` is the mean Equation of the Origins, the result is
350 /// **Greenwich Mean Sidereal Time (GMST)** referred to the mean equinox.
351 ///
352 /// As of Astropy 7.x, this is consistent with
353 /// `Time.sidereal_time("mean").to_value("sec")` (when no longitude is
354 /// specified) when using matching UT1 time and the mean Equation of the Origins.
355 ///
356 /// ## Examples
357 ///
358 /// ```rust
359 /// use deep_time::Sidereal;
360 ///
361 /// let earth = Sidereal::EARTH;
362 /// let mjd = 60000.0;
363 ///
364 /// // Time equivalent of pure Earth Rotation Angle
365 /// let era_seconds = earth.sidereal_time_mean(mjd, 0.0);
366 ///
367 /// // Greenwich Mean Sidereal Time in seconds
368 /// // (requires "sidereal-earth" feature)
369 /// # #[cfg(feature = "sidereal-earth")] {
370 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
371 /// let gmst_seconds = earth.sidereal_time_mean(mjd, eo_mean);
372 /// # }
373 /// ```
374 pub const fn sidereal_time_mean(&self, mjd: Real, eo_rad: Real) -> Real {
375 let angle = self.sidereal_angle_mean(mjd, eo_rad);
376 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
377 fraction * 86400.0
378 }
379
380 /// Returns local sidereal time at the observer's longitude as seconds since
381 /// sidereal midnight, wrapped to the range `[0, 86400)`.
382 ///
383 /// This is the time equivalent of
384 /// [`Sidereal::local_sidereal_angle_mean`].
385 ///
386 /// ## Parameters
387 ///
388 /// - `eo_rad`: The Equation of the Origins value to use.
389 /// - Pass `0.0` to get the time equivalent of the local Earth Rotation Angle (CIO-based).
390 /// - Pass the **mean** Equation of the Origins (e.g. from
391 /// [`Sidereal::earth_eo_mean`](#method.earth_eo_mean)) to obtain **Local Mean Sidereal Time (LMST)**.
392 ///
393 /// ## Details
394 ///
395 /// - When `eo_rad = 0.0`, the result is the time equivalent of the local
396 /// Earth Rotation Angle relative to the Celestial Intermediate Origin (CIO)
397 /// at the observer’s longitude.
398 ///
399 /// - When `eo_rad` is the mean Equation of the Origins, the result is
400 /// **Local Mean Sidereal Time (LMST)** referred to the mean equinox.
401 ///
402 /// As of Astropy 7.x, this is consistent with
403 /// `Time.sidereal_time("mean", longitude=...).to_value("sec")` when using
404 /// matching UT1 time and the mean Equation of the Origins.
405 ///
406 /// ## Examples
407 ///
408 /// ```rust
409 /// use deep_time::Sidereal;
410 ///
411 /// let mut earth = Sidereal::EARTH;
412 /// earth.longitude_rad = 0.0; // Greenwich
413 ///
414 /// let mjd = 60000.0;
415 ///
416 /// // Time equivalent of local Earth Rotation Angle
417 /// let local_era_seconds = earth.local_sidereal_time_mean(mjd, 0.0);
418 ///
419 /// // Local Mean Sidereal Time in seconds
420 /// // (requires "sidereal-earth" feature)
421 /// # #[cfg(feature = "sidereal-earth")] {
422 /// let eo_mean = earth.earth_eo_mean(mjd + 32.184 / 86400.0);
423 /// let lmst_seconds = earth.local_sidereal_time_mean(mjd, eo_mean);
424 /// # }
425 /// ```
426 pub const fn local_sidereal_time_mean(&self, mjd: Real, eo_rad: Real) -> Real {
427 let angle = self.local_sidereal_angle_mean(mjd, eo_rad);
428 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
429 fraction * 86400.0
430 }
431
432 /// Returns the apparent sidereal angle of the body's prime meridian
433 /// in radians, normalized to `[0, 2π)`.
434 ///
435 /// This computes **Greenwich Apparent Sidereal Time (GAST)** when the
436 /// apparent Equation of the Origins is supplied.
437 ///
438 /// ## Parameters
439 ///
440 /// - `eo_rad`: The **apparent** Equation of the Origins
441 /// (e.g. from [`Sidereal::earth_eo_apparent`](#method.earth_eo_apparent)).
442 /// When supplied, the result is Greenwich Apparent Sidereal Time (GAST)
443 /// referred to the true equinox.
444 ///
445 /// ## Details
446 ///
447 /// This function implements the direct relationship:
448 ///
449 /// ```text
450 /// GAST = ERA − EO_apparent
451 /// ```
452 ///
453 /// As of Astropy 7.x, this is consistent with
454 /// `Time.sidereal_time("apparent").rad` (when no longitude is specified)
455 /// when using matching UT1 time and the apparent Equation of the Origins.
456 ///
457 /// ## Examples
458 ///
459 /// ```rust
460 /// use deep_time::Sidereal;
461 ///
462 /// let earth = Sidereal::EARTH;
463 /// let mjd = 60000.0;
464 ///
465 /// // Greenwich Apparent Sidereal Time
466 /// // (requires "sidereal-earth" feature)
467 /// # #[cfg(feature = "sidereal-earth")] {
468 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
469 /// let gast = earth.sidereal_angle_apparent(mjd, eo_app);
470 /// # }
471 /// ```
472 pub const fn sidereal_angle_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
473 let angle = self.rotation_angle(mjd) - eo_rad;
474 Self::normalize_angle(angle)
475 }
476
477 /// Returns the local apparent sidereal angle at the observer's longitude
478 /// in radians, normalized to `[0, 2π)`.
479 ///
480 /// This computes **Local Apparent Sidereal Time (LAST)** when the
481 /// apparent Equation of the Origins is supplied.
482 ///
483 /// ## Parameters
484 ///
485 /// - `eo_rad`: The **apparent** Equation of the Origins
486 /// (e.g. from [`Sidereal::earth_eo_apparent`](#method.earth_eo_apparent)).
487 /// When supplied, the result is Local Apparent Sidereal Time (LAST)
488 /// at the observer’s longitude, referred to the true equinox.
489 ///
490 /// ## Details
491 ///
492 /// This function implements the direct relationship:
493 ///
494 /// ```text
495 /// LAST = ERA + longitude − EO_apparent
496 /// ```
497 ///
498 /// As of Astropy 7.x, this is consistent with
499 /// `Time.sidereal_time("apparent", longitude=...).rad` when using
500 /// matching UT1 time and the apparent Equation of the Origins.
501 ///
502 /// ## Examples
503 ///
504 /// ```rust
505 /// use deep_time::Sidereal;
506 ///
507 /// let mut earth = Sidereal::EARTH;
508 /// earth.longitude_rad = 0.0; // Greenwich
509 ///
510 /// let mjd = 60000.0;
511 ///
512 /// // Local Apparent Sidereal Time
513 /// // (requires "sidereal-earth" feature)
514 /// # #[cfg(feature = "sidereal-earth")] {
515 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
516 /// let last = earth.local_sidereal_angle_apparent(mjd, eo_app);
517 /// # }
518 /// ```
519 pub const fn local_sidereal_angle_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
520 let angle = self.rotation_angle(mjd) + self.longitude_rad - eo_rad;
521 Self::normalize_angle(angle)
522 }
523
524 /// Returns apparent sidereal time at the body's prime meridian as seconds
525 /// since sidereal midnight, wrapped to the range `[0, 86400)`.
526 ///
527 /// This is the time equivalent of
528 /// [`Sidereal::sidereal_angle_apparent`].
529 ///
530 /// When the **apparent** Equation of the Origins is supplied, this function
531 /// returns **Greenwich Apparent Sidereal Time (GAST)**.
532 ///
533 /// ## Parameters
534 ///
535 /// - `eo_rad`: The **apparent** Equation of the Origins
536 /// (e.g. from [`Sidereal::earth_eo_apparent`](#method.earth_eo_apparent)).
537 /// When supplied, the result is Greenwich Apparent Sidereal Time (GAST)
538 /// in seconds since sidereal midnight.
539 ///
540 /// ## Details
541 ///
542 /// This function computes:
543 ///
544 /// ```text
545 /// GAST (seconds) = (ERA − EO_apparent) in fractional days × 86400
546 /// ```
547 ///
548 /// As of Astropy 7.x, this is consistent with
549 /// `Time.sidereal_time("apparent").to_value("sec")` (Greenwich) when using
550 /// matching UT1 time and the apparent Equation of the Origins.
551 ///
552 /// ## Examples
553 ///
554 /// ```rust
555 /// use deep_time::Sidereal;
556 ///
557 /// let earth = Sidereal::EARTH;
558 /// let mjd = 60000.0;
559 ///
560 /// // Greenwich Apparent Sidereal Time in seconds
561 /// // (requires "sidereal-earth" feature)
562 /// # #[cfg(feature = "sidereal-earth")] {
563 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
564 /// let gast_seconds = earth.sidereal_time_apparent(mjd, eo_app);
565 /// # }
566 /// ```
567 pub const fn sidereal_time_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
568 let angle = self.sidereal_angle_apparent(mjd, eo_rad);
569 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
570 fraction * 86400.0
571 }
572
573 /// Returns local apparent sidereal time at the observer's longitude as
574 /// seconds since sidereal midnight, wrapped to the range `[0, 86400)`.
575 ///
576 /// This is the time equivalent of
577 /// [`Sidereal::local_sidereal_angle_apparent`].
578 ///
579 /// When the **apparent** Equation of the Origins is supplied, this function
580 /// returns **Local Apparent Sidereal Time (LAST)**.
581 ///
582 /// ## Parameters
583 ///
584 /// - `eo_rad`: The **apparent** Equation of the Origins
585 /// (e.g. from [`Sidereal::earth_eo_apparent`](#method.earth_eo_apparent)).
586 /// When supplied, the result is Local Apparent Sidereal Time (LAST)
587 /// at the observer’s longitude, in seconds since sidereal midnight.
588 ///
589 /// ## Details
590 ///
591 /// This function computes:
592 ///
593 /// ```text
594 /// LAST (seconds) = (ERA + longitude − EO_apparent) in fractional days × 86400
595 /// ```
596 ///
597 /// As of Astropy 7.x, this is consistent with
598 /// `Time.sidereal_time("apparent", longitude=...).to_value("sec")` when using
599 /// matching UT1 time and the apparent Equation of the Origins.
600 ///
601 /// ## Examples
602 ///
603 /// ```rust
604 /// use deep_time::Sidereal;
605 ///
606 /// let mut earth = Sidereal::EARTH;
607 /// earth.longitude_rad = 0.0; // Greenwich
608 ///
609 /// let mjd = 60000.0;
610 ///
611 /// // Local Apparent Sidereal Time in seconds
612 /// // (requires "sidereal-earth" feature)
613 /// # #[cfg(feature = "sidereal-earth")] {
614 /// let eo_app = earth.earth_eo_apparent(mjd + 32.184 / 86400.0);
615 /// let last_seconds = earth.local_sidereal_time_apparent(mjd, eo_app);
616 /// # }
617 /// ```
618 pub const fn local_sidereal_time_apparent(&self, mjd: Real, eo_rad: Real) -> Real {
619 let angle = self.local_sidereal_angle_apparent(mjd, eo_rad);
620 let fraction = ((angle / TAU) % 1.0 + 1.0) % 1.0;
621 fraction * 86400.0
622 }
623
624 /// Returns the apparent Equation of the Origins (radians) at the given MJD.
625 ///
626 /// This returns the value computed by ERFA’s `eo06a`. It is the modern
627 /// CIO-based quantity used to derive **Greenwich Apparent Sidereal Time (GAST)**
628 /// from the Earth Rotation Angle (ERA).
629 ///
630 /// When you subtract this value from the ERA, you get GAST:
631 ///
632 /// ```text
633 /// GAST = ERA − earth_eo_apparent(...)
634 /// ```
635 ///
636 /// This method is equivalent to calling `erfa.eo06a(tt.jd1, tt.jd2)` in Astropy.
637 ///
638 /// You should pass the value returned by this function to the apparent
639 /// sidereal time functions (`sidereal_angle_apparent`, `local_sidereal_angle_apparent`,
640 /// `sidereal_time_apparent`, and `local_sidereal_time_apparent`).
641 ///
642 /// ## Examples
643 ///
644 /// ```rust
645 /// use deep_time::Sidereal;
646 ///
647 /// let earth = Sidereal::EARTH;
648 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
649 ///
650 /// let eo_app = earth.earth_eo_apparent(mjd_tt);
651 /// let gast = earth.sidereal_angle_apparent(mjd_tt, eo_app);
652 /// ```
653 #[cfg(feature = "sidereal-earth")]
654 #[inline]
655 pub const fn earth_eo_apparent(&self, tt_mjd: Real) -> Real {
656 // Convert MJD → two-part Julian Date
657 let date1 = 2400000.5 + tt_mjd;
658 earth_eo(date1, 0.0)
659 }
660
661 /// Returns the mean Equation of the Origins (radians) at the given MJD.
662 ///
663 /// This returns the value that should be subtracted from the Earth Rotation
664 /// Angle (ERA) to obtain **Greenwich Mean Sidereal Time (GMST)**:
665 ///
666 /// ```text
667 /// GMST = ERA − earth_eo_mean(...)
668 /// ```
669 ///
670 /// Internally, this is computed as:
671 ///
672 /// ```text
673 /// earth_eo_mean = earth_eo_apparent() + earth_ee()
674 /// ```
675 ///
676 /// This is equivalent to computing `era - gmst` in Astropy:
677 ///
678 /// ```python
679 /// era = ut1.earth_rotation_angle(...).rad
680 /// gmst = ut1.sidereal_time("mean", ...).rad
681 /// eo_mean = era - gmst
682 /// ```
683 ///
684 /// You should pass the value returned by this function to the mean
685 /// sidereal time functions (`sidereal_angle_mean`, `local_sidereal_angle_mean`,
686 /// `sidereal_time_mean`, and `local_sidereal_time_mean`).
687 ///
688 /// ## Examples
689 ///
690 /// ```rust
691 /// use deep_time::Sidereal;
692 ///
693 /// let earth = Sidereal::EARTH;
694 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
695 ///
696 /// let eo_mean = earth.earth_eo_mean(mjd_tt);
697 /// let gmst = earth.sidereal_angle_mean(mjd_tt, eo_mean);
698 /// ```
699 #[cfg(feature = "sidereal-earth")]
700 #[inline]
701 pub const fn earth_eo_mean(&self, tt_mjd: Real) -> Real {
702 // Convert MJD → two-part Julian Date
703 let date1 = 2400000.5 + tt_mjd;
704 earth_eo(date1, 0.0) + earth_ee(date1, 0.0)
705 }
706
707 /// Returns the Equation of the Equinoxes (radians) at the given MJD.
708 ///
709 /// This returns the value computed by ERFA’s `ee06a`. The Equation of the
710 /// Equinoxes represents the nutation contribution to sidereal time and is
711 /// defined as:
712 ///
713 /// ```text
714 /// EE = GAST − GMST
715 /// ```
716 ///
717 /// It is equivalent to computing `gast - gmst` in Astropy:
718 ///
719 /// ```python
720 /// gast = ut1.sidereal_time("apparent", ...).rad
721 /// gmst = ut1.sidereal_time("mean", ...).rad
722 /// ee = gast - gmst
723 /// ```
724 ///
725 /// This value is used internally when converting between mean and apparent
726 /// sidereal time (for example, when the mean functions are given the apparent
727 /// EO + EE).
728 ///
729 /// ## Examples
730 ///
731 /// ```rust
732 /// use deep_time::Sidereal;
733 ///
734 /// let earth = Sidereal::EARTH;
735 /// let mjd_tt = 60000.0 + 32.184 / 86400.0;
736 ///
737 /// let ee = earth.earth_ee(mjd_tt);
738 /// ```
739 #[cfg(feature = "sidereal-earth")]
740 #[inline]
741 pub const fn earth_ee(&self, tt_mjd: Real) -> Real {
742 // Convert MJD → two-part Julian Date
743 let date1 = 2400000.5 + tt_mjd;
744 earth_ee(date1, 0.0)
745 }
746}