Skip to main content

kosher_rust/zmanim/
mod.rs

1//! Halachic time calculations (*zmanim*) for a geographic location and civil date.
2//!
3//! This module computes Jewish prayer and ritual times — sunrise, sunset, *alos*,
4//! *tzeis*, *chatzos*, candle lighting, and dozens of other presets aligned with
5//! [KosherJava](https://github.com/KosherJava/zmanim) method names. Results are
6//! [`jiff::Timestamp`] values in UTC; convert to local time with the location's
7//! timezone when presenting them to users.
8//!
9//! # How it fits together
10//!
11//! 1. Build a [`Location`] (latitude, longitude, elevation, optional timezone).
12//! 2. Create a [`ZmanimCalculator`] for that location, a [`jiff::civil::Date`], and
13//!    a [`CalculatorConfig`] that selects elevation and *chatzos* behavior.
14//! 3. Pass any [`ZmanLike`] value to [`ZmanimCalculator::calculate`].
15//!
16//! Most callers use the ready-made constants in [`presets`] (for example
17//! [`presets::ELEVATION_ADJUSTED_SUNRISE`]). Each preset is a [`ZmanPreset`] backed
18//! by a low-level [`ZmanPrimitive`] expression.
19//!
20//! Failures return [`ZmanimError`] — unlike calendar or limud lookups, which use
21//! `Option` when a value simply does not apply on a given date. Some zmanim cannot
22//! be calculated at extreme latitudes or on polar days; others require a timezone
23//! on the location (for example Kiddush Levana via [`molad::MoladCalendar`]).
24//!
25//! # Submodules
26//!
27//! - [`presets`] — named zman constants (`ELEVATION_ADJUSTED_SUNRISE`, `CANDLE_LIGHTING`, …)
28//! - [`types::config`] — [`CalculatorConfig`] options (elevation, *chatzos*, offsets)
29//! - [`types::location`] — [`Location`] validation and timezone requirements
30//! - [`types::error`] — [`ZmanimError`] returned by invalid input or impossible calculations
31//! - [`primitives`] — [`ZmanPrimitive`] building blocks for custom zman definitions
32//! - [`molad`] — molad and Kiddush Levana times via [`molad::MoladCalendar`]
33//!
34//! # Quick start
35//!
36//! ```
37//! use jiff::{civil::Date, tz::TimeZone};
38//! use kosher_rust::zmanim::prelude::*;
39//!
40//! let location = Location::new(
41//!     40.09,
42//!     -74.22,
43//!     0.0,
44//!     Some(TimeZone::get("America/New_York").unwrap()),
45//! )
46//! .unwrap();
47//! let calc = ZmanimCalculator::new(
48//!     location,
49//!     Date::new(2017, 10, 17).unwrap(),
50//!     CalculatorConfig::default(),
51//! );
52//! calc.calculate(&presets::ELEVATION_ADJUSTED_SUNRISE).unwrap();
53//! ```
54//!
55//! Import [`prelude`] (or [`crate::prelude`]) for calculator types, the [`presets`]
56//! module, and [`ZmanPrimitive`] building blocks.
57
58use jiff::{Timestamp, civil::Date};
59
60mod astronomy;
61
62pub mod presets;
63/// Core zmanim types: configuration, errors, and location.
64///
65/// - [`types::config`] — [`CalculatorConfig`]
66/// - [`types::error`] — [`ZmanimError`]
67/// - [`types::location`] — [`Location`]
68pub mod types {
69    /// Calculator configuration options.
70    pub mod config;
71    /// Error types returned by zmanim calculations.
72    pub mod error;
73    /// Geographic location with optional timezone.
74    pub mod location;
75}
76/// Molad and Kiddush Levana time calculations.
77pub mod molad;
78
79pub mod primitives;
80
81/// Common zmanim imports.
82///
83/// Import this module to bring the calculator types, location and config types,
84/// preset constants, and primitive building blocks into scope.
85pub mod prelude {
86    pub use super::presets;
87    pub use super::primitives::ZmanPrimitive;
88    pub use super::types::config::CalculatorConfig;
89    pub use super::types::error::ZmanimError;
90    pub use super::types::location::Location;
91    pub use super::{ZmanLike, ZmanPreset, ZmanimCalculator};
92}
93
94#[cfg(test)]
95mod tests;
96
97use crate::zmanim::{
98    primitives::ZmanPrimitive,
99    types::{config::CalculatorConfig, error::ZmanimError, location::Location},
100};
101
102#[cfg(feature = "alloc")]
103extern crate alloc;
104
105#[cfg(feature = "alloc")]
106use alloc::string::String;
107
108/// Calculates zmanim for a [`Location`] on a specific [`Date`].
109///
110/// Create one calculator for a location and date, then pass any [`ZmanLike`]
111/// value to [`ZmanimCalculator::calculate`].
112///
113/// Most callers should use the ready-made definitions in [`presets`],
114/// such as `presets::ELEVATION_ADJUSTED_SUNRISE`, instead of writing custom zman logic.
115#[derive(Clone, Debug)]
116pub struct ZmanimCalculator {
117    /// The location to calculate for.
118    pub(crate) location: Location,
119    /// The civil date at the configured location.
120    pub(crate) date: Date,
121    /// Options that control calculation behavior.
122    pub(crate) config: CalculatorConfig,
123}
124
125impl ZmanimCalculator {
126    /// Creates a calculator for the given `location`, `date`, and `config`.
127    ///
128    /// Use this before calculating zmanim for that location and date.
129    pub fn new(location: Location, date: Date, config: CalculatorConfig) -> Self {
130        Self { location, date, config }
131    }
132
133    /// Calculates a single zman.
134    pub fn calculate(&self, zman: &impl ZmanLike) -> Result<Timestamp, ZmanimError> {
135        zman.calculate(self)
136    }
137}
138
139/// A zman definition that can be calculated by a [`ZmanimCalculator`].
140///
141/// Prefer the predefined values in [`presets`]. Implement this trait
142/// only when you need a custom zman definition.
143pub trait ZmanLike {
144    /// Calculates this zman with the given calculator.
145    ///
146    /// Custom zman definitions should put their calculation logic here.
147    fn calculate(&self, calculator: &ZmanimCalculator) -> Result<Timestamp, ZmanimError>;
148}
149/// A named zman preset backed by a low-level [`ZmanPrimitive`].
150///
151/// Most callers should use presets directly instead of constructing
152/// [`ZmanPrimitive`] values themselves.
153#[derive(Debug, Clone)]
154pub struct ZmanPreset {
155    /// The primitive calculation used by this preset.
156    pub event: ZmanPrimitive,
157    /// The KosherJava getter name (for example `getSunrise`).
158    pub method_name: &'static str,
159    /// The user-facing preset name.
160    pub name: &'static str,
161    #[cfg(feature = "alloc")]
162    pub(crate) description: fn(&ZmanimCalculator) -> String,
163    /// Whether KosherJava marks this preset as deprecated.
164    pub deprecated: bool,
165}
166
167impl ZmanLike for ZmanPreset {
168    fn calculate(&self, calculator: &ZmanimCalculator) -> Result<Timestamp, ZmanimError> {
169        self.event.calculate(calculator)
170    }
171}
172
173impl ZmanPreset {
174    /// Returns a user-facing preset description.
175    ///
176    /// The description includes active [`ZmanimCalculator`] settings when they
177    /// affect how this preset is calculated.
178    ///
179    /// Requires the `alloc` feature.
180    #[cfg(feature = "alloc")]
181    pub fn description(&self, calculator: &ZmanimCalculator) -> String {
182        let mut desc = (self.description)(calculator);
183
184        if self.event.uses_elevation_from_config() {
185            if calculator.config.use_elevation {
186                desc.push_str(
187                    "\n\nThis zman takes the configured location's elevation into account when it is calculated.",
188                );
189            } else {
190                desc.push_str("\n\nThis zman is calculated as though the configured location were at sea level.");
191            }
192        }
193        if self.event.uses_astronomical_chatzos_from_config() {
194            if calculator.config.use_astronomical_chatzos {
195                desc.push_str("\n\nThis zman uses astronomical chatzos (solar transit) as the midpoint of the day.");
196            } else {
197                desc.push_str("\n\nThis zman uses the midpoint between sunrise and sunset for chatzos.");
198            }
199        }
200        if self.event.uses_astronomical_chatzos_for_other_zmanim_from_config() {
201            if calculator.config.use_astronomical_chatzos_for_other_zmanim {
202                desc.push_str(
203                    "\n\nThis zman divides the afternoon from astronomical chatzos when calculating shaos zmaniyos.",
204                );
205            } else {
206                desc.push_str(
207                    "\n\nThis zman uses a fixed fraction of the sunrise-to-sunset interval for shaos zmaniyos, without dividing the day at astronomical chatzos.",
208                );
209            }
210        }
211        desc
212    }
213}
214
215#[cfg(feature = "defmt")]
216impl defmt::Format for ZmanimCalculator {
217    fn format(&self, fmt: defmt::Formatter) {
218        let y = self.date.year();
219        let m = self.date.month();
220        let d = self.date.day();
221        defmt::write!(
222            fmt,
223            "ZmanimCalculator {{ location: {}, date: {=i16}-{=i8}-{=i8}, config: {} }}",
224            self.location,
225            y,
226            m,
227            d,
228            self.config
229        )
230    }
231}
232#[cfg(feature = "defmt")]
233impl defmt::Format for ZmanPreset {
234    fn format(&self, fmt: defmt::Formatter) {
235        defmt::write!(fmt, "ZmanPreset {{ event: {}, name: {} }}", self.event, self.name)
236    }
237}