use chrono::{NaiveDate, Local, Datelike};
use std::fmt;
pub struct RD {
pub rd: i32
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GDate {
pub year: i32,
pub month: u8, pub day: u8, }
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct JDate {
pub year: i32,
pub month: u8, pub day: u8, }
impl GDate {
pub fn new(year: i32, month: u8, day: u8) -> Option<GDate> {
NaiveDate::from_ymd_opt(year, month as u32, day as u32)
.map(|_| GDate{year: year, month: month, day: day})
}
pub fn today() -> GDate {
let date = Local::now().date_naive();
GDate{
year: date.year(),
month: date.month() as u8,
day: date.day() as u8
}
}
}
impl JDate {
pub fn new(year: i32, month: u8, day: u8) -> Option<JDate> {
match date_is_valid(year, month, day) {
true => Some(JDate{year: year, month: month, day: day}),
false => None
}
}
pub fn today() -> JDate {
JDate::from(GDate::today())
}
pub fn month_name(self: Self) -> &'static str {
const NAMES: [&str; 13] = [
"Nisan", "Iyar", "Sivan", "Tamuz", "Av", "Elul",
"Tishrei", "Cheshvan", "Kislev", "Tevet", "Shvat", "Adar", "Adar2"];
if self.month == 12 && is_leap_year(self.year) {
return "Adar1"
}
return NAMES[self.month as usize - 1];
}
pub fn dafyomi(self: Self) -> (&'static str, u8) {
let tractates = [
("Berachos", 63),
("Shabbos", 156),
("Eruvin", 104),
("Pesachim", 120),
("Shekalim", 21),
("Yoma", 87),
("Sukah", 55),
("Beitzah", 39),
("Rosh Hashanah", 34),
("Taanis", 30),
("Megilah", 31),
("Moed Katan", 28),
("Chagigah", 26),
("Yevamos", 121),
("Kesuvos", 111),
("Nedarim", 90),
("Nazir", 65),
("Sotah", 48),
("Gitin", 89),
("Kidushin", 81),
("Bava Kama", 118),
("Bava Metzia", 118),
("Bava Basra", 175),
("Sanhedrin", 112),
("Makos", 23),
("Shevuos", 48),
("Avodah Zarah", 75),
("Horayos", 13),
("Zevachim", 119),
("Menachos", 109),
("Chulin", 141),
("Bechoros", 60),
("Erchin", 33),
("Temurah", 33),
("Kerisus", 27),
("Meilah", 36),
("Nidah", 72),
];
let rd = RD::from(self).rd;
let mut day = (rd - 37).rem_euclid(2711);
for (trac, pages) in tractates.iter() {
if day <= pages - 1 {
return (trac, (day+2) as u8);
}
day -= pages;
}
return ("", 0);
}
pub fn omer(self: Self) -> u8 {
let day = self.day;
let month = self.month;
if month == 1 && day >= 16 {
return day - 15;
} else if month == 2 {
return day + 15;
} else if month == 3 && day < 6 {
return day + 44;
} else {
return 0;
}
}
}
impl fmt::Display for GDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:0>4}-{:0>2}-{:0>2}", self.year, self.month, self.day)
}
}
impl fmt::Display for JDate {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:0>4}-{}-{:0>2}", self.year, self.month_name(), self.day)
}
}
impl From<RD> for JDate {
fn from(rd: RD) -> JDate {
let ed = rd.rd + 1373428; let mut year = ed * 100 / 36525;
while year_start(year) < ed {year += 1;}
while year_start(year) > ed {year -= 1;}
let mut days = year_start(year);
let days_in_month = year_months(year);
let mut month = 7;
loop {
let length = days_in_month[month] as i32;
if days + length > ed {break}
month += 1;
if month == 14 {month = 1}
if month == 7 {unreachable!()}
days += length;
}
return JDate{
year: year,
month: month as u8,
day: (ed-days+1) as u8
};
}
}
impl From<JDate> for RD {
fn from(j: JDate) -> RD {
let mut ed = year_start(j.year) - 1;
let days_in_month = year_months(j.year);
let mut month: u8 = 7;
loop {
let length = days_in_month[month as usize];
if month == j.month {
ed += j.day as i32;
break;
}
ed += length as i32;
month += 1;
if month == 14 {month = 1}
if month == 7 {unreachable!()}
}
RD{rd: ed - 1373428}
}
}
impl From<RD> for GDate {
fn from(rd: RD) -> GDate {
let date = NaiveDate::from_epoch_days(rd.rd - 719163).unwrap();
GDate{
year: date.year(),
month: date.month() as u8,
day: date.day() as u8
}
}
}
impl From<GDate> for RD {
fn from(g: GDate) -> RD {
RD{
rd: NaiveDate::from_ymd_opt(g.year, g.month as u32, g.day as u32)
.unwrap()
.to_epoch_days()
+ 719163
}
}
}
impl From<GDate> for JDate {
fn from(d: GDate) -> JDate {
JDate::from(RD::from(d))
}
}
impl From<JDate> for GDate {
fn from(j: JDate) -> Self {
GDate::from(RD::from(j))
}
}
pub fn is_leap_year(year: i32) -> bool {
match year % 19 {
0|3|6|8|11|14|17 => true,
_ => false
}
}
pub fn date_is_valid(year: i32, month: u8, day: u8) -> bool {
if month < 1 || month > 13 || day < 1 || day > 30 {
return false;
}
if month == 13 {
return day <= 29 && is_leap_year(year);
}
if day == 30 {
return match month {
1|3|5|7|11 => true,
2|4|6|10 => false,
12 => is_leap_year(year),
8 => {
year_length(year) % 10 == 5 },
9 => {
year_length(year) % 10 >= 4 },
_ => unreachable!()
}
}
true
}
pub fn molad(year: i32) -> i64 {
let parts_month = (29*24+12)*1080+793;
let parts_year = 12 * parts_month;
let parts_lyear = 13 * parts_month;
let parts_cycle = 12 * parts_year + 7 * parts_lyear;
let total_cycles = (year-1).div_euclid(19);
let year_in_cycle = (year-1).rem_euclid(19);
let mut molad: i64 = (24+5)*1080+204; molad += total_cycles as i64 * parts_cycle as i64;
for year in 0..year_in_cycle {
if is_leap_year(year+1) {
molad += parts_lyear as i64;
} else {
molad += parts_year as i64;
}
}
return molad;
}
pub fn molad_components(year: i32) -> (i32, u8, u16) {
let molad = molad(year);
return ((molad.div_euclid(1080*24)) as i32,
(molad.div_euclid(1080).rem_euclid(24)) as u8,
(molad.rem_euclid(1080)) as u16);
}
pub fn molad_print(year: i32) {
let (day, hour, parts) = molad_components(year);
let day = day % 7;
let hour = (hour + 18) % 24;
let minute = parts / 18;
let parts = parts % 18;
let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
let day_str = days[day as usize];
println!("Molad {year}: {day_str} {hour:0>2}:{minute:0>2} and {parts:>2} \
chalakim");
}
pub fn year_start(year: i32) -> i32 {
let molad = molad(year);
let day = molad.div_euclid(1080*24) as i32;
let parts = molad.rem_euclid(1080*24) as i32;
let mut rosh = day;
if parts >= 18*1080 {
rosh += 1;
}
if rosh % 7 == 0 || rosh % 7 == 3 || rosh % 7 == 5 {
rosh += 1;
}
if !is_leap_year(year) && day % 7 == 2 && parts >= 9*1080+204 {
rosh = day+2;
}
if is_leap_year(year-1) && day % 7 == 1 && parts >= 15*1080+589 {
rosh = day+1;
}
return rosh;
}
pub fn year_length(year: i32) -> i32 {
let rosh1 = year_start(year);
let rosh2 = year_start(year+1);
return rosh2-rosh1;
}
pub fn year_months(year: i32) -> [u8; 14] {
let mut days_in_month = [0, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 0]; if is_leap_year(year) {
days_in_month[12] = 30; days_in_month[13] = 29;
}
let length = year_length(year);
if length % 10 == 3 {
days_in_month[9] = 29;
}
if length % 10 == 5 {
days_in_month[8] = 30;
}
return days_in_month;
}
#[cfg(test)]
mod tests {
use super::*;
fn gdate(year: i32, month: u8, day: u8) -> GDate {
GDate::new(year, month, day).unwrap()
}
fn jdate(year: i32, month: u8, day: u8) -> JDate {
JDate::new(year, month, day).unwrap()
}
fn check_roundtrip(g: GDate, j: JDate) {
assert_eq!(JDate::from(g), j);
assert_eq!(GDate::from(j), g);
}
#[test]
fn test_molad_year() {
assert_eq!(molad_components(1), (1, 5, 204));
assert_eq!(molad_components(5785), (2112590, 9, 391));
}
#[test]
fn test_leap_year() {
assert!(is_leap_year(5700));
assert!(!is_leap_year(5701));
assert!(!is_leap_year(5702));
assert!(is_leap_year(5703));
assert!(is_leap_year(5782));
assert!(!is_leap_year(5783));
assert!(is_leap_year(5784));
assert!(!is_leap_year(5785));
assert!(!is_leap_year(5786));
assert!(is_leap_year(5787));
}
#[test]
fn test_jdate_new() {
assert!(JDate::new(5785, 1, 1).is_some());
assert!(JDate::new(5784, 12, 30).is_some());
assert!(JDate::new(5784, 13, 1).is_some());
assert!(JDate::new(5786, 9, 30).is_some());
assert!(JDate::new(5787, 9, 30).is_some());
assert!(JDate::new(5785, 13, 1).is_none());
assert!(JDate::new(5785, 0, 1).is_none());
assert!(JDate::new(5785, 14, 1).is_none());
assert!(JDate::new(5785, 1, 0).is_none());
assert!(JDate::new(5785, 1, 31).is_none());
assert!(JDate::new(5785, 2, 30).is_none());
assert!(JDate::new(5785, 12, 30).is_none());
assert!(JDate::new(5785, 13, 30).is_none());
assert!(JDate::new(5781, 8, 30).is_none());
assert!(JDate::new(5781, 9, 30).is_none());
assert!(JDate::new(5786, 8, 30).is_none());
}
#[test]
fn test_roundtrip() {
check_roundtrip(gdate( 1, 1, 1), jdate(3761, 10, 18));
check_roundtrip(gdate(-3760, 9, 7), jdate( 1, 7, 1));
check_roundtrip(gdate(2024, 12, 31), jdate(5785, 9, 30));
check_roundtrip(gdate(2025, 1, 1), jdate(5785, 10, 1));
check_roundtrip(gdate(2025, 2, 1), jdate(5785, 11, 3));
check_roundtrip(gdate(2025, 3, 1), jdate(5785, 12, 1));
check_roundtrip(gdate(2024, 2, 10), jdate(5784, 12, 1));
check_roundtrip(gdate(2024, 3, 11), jdate(5784, 13, 1));
check_roundtrip(gdate(2024, 4, 9), jdate(5784, 1, 1));
check_roundtrip(gdate(2024, 10, 2), jdate(5784, 6, 29));
check_roundtrip(gdate(2024, 10, 3), jdate(5785, 7, 1));
}
#[test]
fn test_dafyomi() {
assert_eq!(jdate(5772, 5, 15).dafyomi(), ("Berachos", 2));
assert_eq!(jdate(5780, 10, 8).dafyomi(), ("Berachos", 2));
assert_eq!(jdate(5780, 12, 11).dafyomi(), ("Berachos", 64));
assert_eq!(jdate(5780, 12, 12).dafyomi(), ("Shabbos", 2));
assert_eq!(jdate(5784, 7, 6).dafyomi(), ("Kidushin", 39));
assert_eq!(jdate(5787, 3, 2).dafyomi(), ("Nidah", 73));
}
#[test]
fn test_omer() {
assert_eq!(jdate(5785, 1, 15).omer(), 0);
assert_eq!(jdate(5785, 1, 16).omer(), 1);
assert_eq!(jdate(5785, 1, 30).omer(), 15);
assert_eq!(jdate(5785, 2, 1).omer(), 16);
assert_eq!(jdate(5785, 2, 29).omer(), 44);
assert_eq!(jdate(5785, 3, 1).omer(), 45);
assert_eq!(jdate(5785, 3, 5).omer(), 49);
assert_eq!(jdate(5785, 3, 6).omer(), 0);
assert_eq!(jdate(5785, 4, 1).omer(), 0);
assert_eq!(jdate(5785, 1, 1).omer(), 0);
}
}