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}