1use chrono::{DateTime, Duration, Utc};
2
3use crate::prelude::*;
4use serde::ser::SerializeStruct;
5use serde::Serialize;
6use std::num::NonZeroI8;
7
8mod year;
9#[doc(inline)]
10pub use year::*;
11
12#[derive(Debug, Copy, Clone)]
13pub struct HebrewDate {
15 day: NonZeroI8,
16 month: HebrewMonth,
17 year: HebrewYear,
18}
19impl Serialize for HebrewDate {
20 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
21 where
22 S: serde::ser::Serializer,
23 {
24 let mut state = serializer.serialize_struct("HebrewDate", 3)?;
25 state.serialize_field("day", &self.day.get())?;
26 state.serialize_field("month", &self.month)?;
27 state.serialize_field("year", &self.year())?;
28 state.end()
29 }
30}
31impl Eq for HebrewDate {}
32impl PartialEq for HebrewDate {
33 fn eq(&self, other: &HebrewDate) -> bool {
34 self.day == other.day && self.month == other.month && self.year() == other.year()
35 }
36}
37
38use std::cmp::Ordering;
39impl Ord for HebrewDate {
40 fn cmp(&self, other: &HebrewDate) -> Ordering {
41 if self.year() < other.year() {
42 Ordering::Less
43 } else if self.year() > other.year() {
44 Ordering::Greater
45 } else if (self.month as i32) < (other.month as i32) {
46 Ordering::Less
47 } else if (self.month as i32) > (other.month as i32) {
48 Ordering::Greater
49 } else if self.day < other.day {
50 Ordering::Less
51 } else if self.day > other.day {
52 Ordering::Greater
53 } else {
54 Ordering::Equal
55 }
56 }
57}
58
59impl PartialOrd for HebrewDate {
60 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
61 Some(self.cmp(other))
62 }
63}
64
65impl HebrewDate {
66 pub fn from_ymd(
87 year: u64,
88 month: HebrewMonth,
89 day: NonZeroI8,
90 ) -> Result<HebrewDate, ConversionError> {
91 HebrewYear::new(year)?.get_hebrew_date(month, day)
92 }
93
94 pub(crate) fn from_ymd_internal(
95 month: HebrewMonth,
96 day: NonZeroI8, hebrew_year: HebrewYear,
98 ) -> Result<HebrewDate, ConversionError> {
99 if day.get() < 0 {
100 return Err(ConversionError::TooManyDaysInMonth(
101 hebrew_year.sched[month as usize],
102 )); }
104 if !hebrew_year.is_leap_year()
107 && (month == HebrewMonth::Adar1 || month == HebrewMonth::Adar2)
108 {
109 return Err(ConversionError::IsNotLeapYear);
110 }
111
112 if hebrew_year.is_leap_year() && month == HebrewMonth::Adar {
113 return Err(ConversionError::IsLeapYear);
114 }
115
116 if day.get() as u8 > hebrew_year.sched[month as usize] {
117 return Err(ConversionError::TooManyDaysInMonth(
118 hebrew_year.sched[month as usize],
119 ));
120 }
121
122 Ok(HebrewDate {
123 month,
124 day,
125 year: hebrew_year,
126 })
127 }
128
129 fn from_gregorian(date: DateTime<Utc>) -> Result<HebrewDate, ConversionError> {
130 if date < *crate::convert::year::backend::FIRST_RH + Duration::days(2 + 365) {
131 return Err(ConversionError::YearTooSmall);
132 }
133 let days_since_first_rh =
134 ((date - *crate::convert::year::backend::FIRST_RH).num_days() + 2) as u64;
135
136 let hebrew_year = HebrewYear::new(crate::convert::year::backend::day_of_last_rh(
137 days_since_first_rh,
138 ))
139 .unwrap();
140 Ok(hebrew_year.get_hebrewdate_from_days_after_rh(days_since_first_rh))
141 }
142
143 pub(crate) fn to_gregorian(&self) -> DateTime<Utc> {
144 let amnt_days_between_rh_and_epoch = self.year.days_since_epoch;
145 let sched = self.year.sched;
146 let mut amnt_days_in_month: u16 = 0;
147 if self.month != HebrewMonth::Tishrei {
148 for item in sched.iter().take(self.month as usize) {
149 amnt_days_in_month += u16::from(*item);
150 }
151 }
152
153 let amnt_days =
154 amnt_days_between_rh_and_epoch + u64::from(amnt_days_in_month) + self.day.get() as u64
155 - 1;
156 *crate::convert::year::backend::EPOCH + Duration::days(amnt_days as i64)
157 }
158 #[inline]
160 pub fn day(&self) -> NonZeroI8 {
161 self.day
162 }
163
164 #[inline]
166 pub fn month(&self) -> HebrewMonth {
167 self.month
168 }
169
170 #[inline]
173 pub fn year(&self) -> u64 {
174 self.year.year
175 }
176}
177
178mod tests {
179 #[test]
180 fn get_year() {
181 use super::*;
182 use chrono::prelude::*;
183 for j in 0..100 {
184 let mut original_day = Utc.ymd(16 + j, 10, 4).and_hms(18, 0, 0);
185 for _i in 1..366 {
186 let h_day = HebrewDate::from_gregorian(original_day).unwrap();
187 let ne_day = h_day.to_gregorian();
188 assert_eq!(original_day, ne_day);
189 original_day = original_day + Duration::days(1);
190 }
191 }
192 }
193
194 #[test]
195 fn from_ymd_negative() {
196 use crate::prelude::*;
197 use crate::HebrewYear;
198 use std::num::NonZeroI8;
199 let a: ConversionError = HebrewYear::new(5779)
200 .unwrap()
201 .get_hebrew_date(HebrewMonth::Shvat, NonZeroI8::new(-1).unwrap())
202 .unwrap_err();
203 if let ConversionError::TooManyDaysInMonth(_) = a {
204 } else {
205 panic!("A is not a TooManyDaysInMonth");
206 }
207 }
208}