-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhijri_date.rs
277 lines (243 loc) · 7.22 KB
/
hijri_date.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
use std::fmt::Display;
use chrono::{Datelike, NaiveDate};
use crate::error::OutOfRangeError;
/// Hijri day of the week.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HijriDay {
/// Sunday
Ahad = 1,
/// Monday
Ithnain,
/// Tuesday
Thulatha,
/// Wednesday
Arbiaa,
/// Thursday
Khamees,
/// Friday
Jumaah,
/// Saturday
Sabt,
}
impl Display for HijriDay {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
impl TryFrom<u8> for HijriDay {
type Error = OutOfRangeError<u8>;
fn try_from(value: u8) -> Result<Self, Self::Error> {
use HijriDay::*;
match value {
1 => Ok(Ahad),
2 => Ok(Ithnain),
3 => Ok(Thulatha),
4 => Ok(Arbiaa),
5 => Ok(Khamees),
6 => Ok(Jumaah),
7 => Ok(Sabt),
_ => Err(OutOfRangeError(1..=7)),
}
}
}
/// Hijri month of the year.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HijriMonth {
/// First month of the year
Muharram = 1,
/// Second month of the year
Safar,
/// Third month of the year
RabiaAwal,
/// Fourth month of the year
RabiaThani,
/// Fifth month of the year
JumadaAwal,
/// Sixth month of the year
JumadaThani,
/// Seventh month of the year
Rajab,
/// Eighth month of the year
Shaaban,
/// Ninth month of the year
Ramadan,
/// Tenth month of the year
Shawwal,
/// Eleventh month of the year
DhulQiddah,
/// Twelfth month of the year
DhulHijjah,
}
impl TryFrom<u8> for HijriMonth {
type Error = OutOfRangeError<u8>;
fn try_from(value: u8) -> Result<Self, Self::Error> {
use HijriMonth::*;
match value {
1 => Ok(Muharram),
2 => Ok(Safar),
3 => Ok(RabiaAwal),
4 => Ok(RabiaThani),
5 => Ok(JumadaAwal),
6 => Ok(JumadaThani),
7 => Ok(Rajab),
8 => Ok(Shaaban),
9 => Ok(Ramadan),
10 => Ok(Shawwal),
11 => Ok(DhulQiddah),
12 => Ok(DhulHijjah),
_ => Err(OutOfRangeError(1..=12)),
}
}
}
impl Display for HijriMonth {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use HijriMonth::*;
let default = format!("{:?}", self);
let val = match self {
RabiaAwal => "Rabia Awal",
RabiaThani => "Rabia Thani",
JumadaAwal => "Jumada Awal",
JumadaThani => "Jumada Thani",
DhulQiddah => "Dhul Qiddah",
DhulHijjah => "Dhul Hijjah",
_ => &default,
};
write!(f, "{}", val)
}
}
/// A date in the Hijri calender.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HijriDate {
date: NaiveDate,
day: u8,
month: u8,
year: u32,
pre_epoch: bool,
weekday: u8,
}
impl HijriDate {
const HIJRI_EPOCH: i32 = 227015;
/// Returns the ISO 8601 calendar date without timezone for the Hijri date.
pub fn date(&self) -> NaiveDate {
self.date
}
/// Returns the Hijri day of the month.
pub fn day(&self) -> u8 {
self.day
}
/// Returns the Hijri day of the week.
pub fn day_of_week(&self) -> HijriDay {
HijriDay::try_from(self.weekday).unwrap()
}
/// Returns the Hijri month of the year.
pub fn month(&self) -> HijriMonth {
HijriMonth::try_from(self.month).unwrap()
}
/// Returns the Hijri year.
pub fn year(&self) -> u32 {
self.year
}
/// Returns true when the Hijri date is before Hijra, false otherwise.
pub fn pre_epoch(&self) -> bool {
self.pre_epoch
}
// Calculates and returns the absolute Gregorian date for the date.
fn greg_abs_date(date: NaiveDate) -> i32 {
let y_1 = (date.year() - 1) as f64;
(date.ordinal() as f64 + 365. * y_1 + (y_1 / 4.).floor() - (y_1 / 100.).floor()
+ (y_1 / 400.).floor()) as i32
}
// Calculates and returns the absolute Hijri date for the day, month, and year.
fn hijri_abs_date(day: u8, month: u8, year: i32) -> i32 {
let day: f64 = day as f64;
let month: f64 = month as f64;
let year: f64 = year as f64;
(day + 29. * (month - 1.)
+ (month / 2.).floor()
+ 354. * (year - 1.)
+ ((3. + 11. * year) / 30.).floor()
+ Self::HIJRI_EPOCH as f64
- 1.) as i32
}
// Calculates and returns the Hijri year for the absolute Gregorian date.
fn hijri_year(greg_date: i32) -> i32 {
let mut year: i32;
if greg_date < Self::HIJRI_EPOCH {
year = 0;
while greg_date <= Self::hijri_abs_date(1, 1, year) {
year -= 1;
}
} else {
year = ((greg_date - Self::HIJRI_EPOCH - 1) as f64 / 355.).floor() as i32;
while greg_date >= Self::hijri_abs_date(1, 1, year + 1) {
year += 1;
}
}
year
}
// Calculates and returns the Hirji month value for the absolute Gregorian date and year.
fn month_val(greg_date: i32, year: i32) -> u8 {
let mut month = 1;
while greg_date > Self::hijri_abs_date(Self::days_in_month(month, year), month, year) {
month += 1;
}
month
}
// Calculates and returns the number of days for the Hijri month and year.
fn days_in_month(month: u8, year: i32) -> u8 {
if month % 2 != 1 && (month != 12 || !Self::is_hijri_leap_year(year)) {
29
} else {
30
}
}
// Calculates and returns true if the year is a Hijri leap year, false otherwise.
fn is_hijri_leap_year(year: i32) -> bool {
((11 * year).abs() + 14) % 30 < 11
}
// Adjusts the Hijri year value for pre-epoch and returns the adjusted year and whether it is pre-epoch.
fn adj_pre_epoch(mut year: i32) -> (u32, bool) {
let mut pre_epoch = false;
if year <= 0 {
pre_epoch = true;
year = -(year - 1);
}
(year as u32, pre_epoch)
}
}
impl From<NaiveDate> for HijriDate {
fn from(value: NaiveDate) -> Self {
let adj_date = if value.year() < 0 {
NaiveDate::from_ymd_opt(value.year() + 1, value.month(), value.day()).unwrap()
} else {
value
};
let greg_date = Self::greg_abs_date(adj_date);
let year = Self::hijri_year(greg_date);
let month = Self::month_val(greg_date, year);
let day = (greg_date - Self::hijri_abs_date(1, month, year) + 1) as u8;
let (year, pre_epoch) = Self::adj_pre_epoch(year);
let weekday = ((greg_date % 7).abs() + 1) as u8;
Self {
date: value,
year,
month,
day,
pre_epoch,
weekday,
}
}
}
impl Display for HijriDate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}, {} {}, {} {}",
self.day_of_week(),
self.month(),
self.day,
self.year,
if self.pre_epoch { "B.H." } else { "A.H." }
)
}
}