1use chrono::Datelike;
2use parsidate::{ParsiDate, ParsiDateTime, DateError};
3
4#[derive(Debug)]
5pub enum Script {
6 Latin,
7 Persian,
8 English,
9}
10
11const PERSIAN_DAY_NAMES : [&str; 7] = ["شنبه", "یکشنبه", "دوشنبه", "سهشنبه", "چهارشنبه", "پنجشنبه", "جمعه"];
12const LATIN_DAY_NAMES : [&str; 7] = ["Shanbeh", "Yek-Shanbeh", "Do-Shanbeh", "Seh-Shanbeh", "Chahar-Shanbeh", "Panj-Shanbeh", "Jomeh"];
13const ENGLISH_DAY_NAMES : [&str; 7] = ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
14const PERSIAN_MONTH_NAMES : [&str; 12] = ["فروردین", "اردیبهشت", "خرداد", "تیر", "مرداد", "شهریور", "مهر", "آبان", "آذر", "دی", "بهمن", "اسفند"];
15const LATIN_MONTH_NAMES : [&str; 12] = ["Farvardin", "Ordibehesht", "Khordad", "Tir", "Mordad", "Shahrivar", "Mehr", "Aban", "Azar", "Dei", "Bahman", "Esfand"];
16
17pub trait DateTimeFormat {
18 fn format_datetime(&self, pattern: &str, script: &Script) -> String;
19}
20
21pub trait DateHelper {
22 fn day_name_in(&self, script: &Script) -> Result<&'static str, DateError>;
23 fn month_name_in(&self, script: &Script) -> &'static str;
24 fn month_first_day(&self) -> Result<usize, DateError>;
25 fn weekday_from_saturday(&self) -> Result<usize, DateError>;
26 fn next_month(&self) -> Result<ParsiDate, DateError>;
27 fn prev_month(&self) -> Result<ParsiDate, DateError>;
28}
29
30impl DateHelper for ParsiDate {
31 fn day_name_in(&self, script: &Script) -> Result<&'static str, DateError> {
32 let day_index = self.weekday_from_saturday()?;
33
34 Ok(match script {
35 Script::Latin => LATIN_DAY_NAMES[day_index],
36 Script::English => ENGLISH_DAY_NAMES[day_index],
37 Script::Persian => PERSIAN_DAY_NAMES[day_index],
38 })
39 }
40
41 fn month_name_in(&self, script: &Script) -> &'static str {
42 let month_index = self.month() as usize - 1;
43
44 match script {
45 Script::Latin | Script::English => LATIN_MONTH_NAMES[month_index],
46 Script::Persian => PERSIAN_MONTH_NAMES[month_index],
47 }
48 }
49
50 fn month_first_day(&self) -> Result<usize, DateError> {
51 self.first_day_of_month().weekday_from_saturday()
52 }
53
54 fn weekday_from_saturday(&self) -> Result<usize, DateError> {
55 let weekday_from_monday = self.to_gregorian()?.weekday() as usize;
56
57 Ok((weekday_from_monday + 2) % 7)
58 }
59
60 fn next_month(&self) -> Result<ParsiDate, DateError> {
61 self.add_months(1)
62 }
63
64 fn prev_month(&self) -> Result<ParsiDate, DateError> {
65 self.sub_months(1)
66 }
67}
68
69impl DateTimeFormat for ParsiDateTime {
70 fn format_datetime(&self, pattern: &str, script: &Script) -> String {
71 let modified_pattern = pattern
72 .replace("%R", "%H:%M")
73 .replace("%X", "%H:%M:%S")
74 .replace("%F", "%Y-%m-%d")
75 .replace("%x", "%Y/%m/%d")
76 .replace("%t", "\t")
77 .replace("%n", "\n")
78 .replace("%e", &format!("{:2}", self.day()))
79 .replace("%A", self.date().day_name_in(script).unwrap_or("%A"))
80 .replace("%B", self.date().month_name_in(script));
81
82 self.format(&modified_pattern)
83 }
84}
85
86pub fn replace_with_persian_numbers(text: String) -> String {
87 text
88 .replace('0', "۰")
89 .replace('1', "۱")
90 .replace('2', "۲")
91 .replace('3', "۳")
92 .replace('4', "۴")
93 .replace('5', "۵")
94 .replace('6', "۶")
95 .replace('7', "۷")
96 .replace('8', "۸")
97 .replace('9', "۹")
98}
99
100pub fn abbreviated_day_names(script: &Script) -> Vec<&'static str> {
101 let (day_names, bytes) = match script {
102 Script::Latin => (LATIN_DAY_NAMES, 2),
103 Script::English => (ENGLISH_DAY_NAMES, 2),
104 Script::Persian => (PERSIAN_DAY_NAMES, 2 * 2),
105 };
106
107 day_names.iter().map(|day| &day[..bytes]).collect::<Vec<_>>()
108}
109
110#[cfg(not(fake_date))]
111pub fn date_now() -> Result<ParsiDateTime, DateError> {
112 ParsiDateTime::now()
113}
114#[cfg(fake_date)]
115pub fn date_now() -> Result<ParsiDateTime, DateError> {
116 ParsiDateTime::new(1404, 1, 16, 22, 55, 30)
118}
119
120#[cfg(not(fake_date))]
121pub fn date_today() -> Result<ParsiDate, DateError> {
122 ParsiDate::today()
123}
124#[cfg(fake_date)]
125pub fn date_today() -> Result<ParsiDate, DateError> {
126 ParsiDate::new(1404, 1, 16)
128}
129
130#[cfg(test)]
131mod tests {
132 #[allow(unused)]
133 use super::*;
134
135 #[cfg(not(fake_date))]
136 #[test]
137 fn test_date_format() -> Result<(), Box<dyn std::error::Error>> {
138 use predicates::prelude::{predicate, Predicate};
139
140 let date = date_now();
141 let script = Script::Latin;
142
143 let predicate = predicate::str::is_match(r"\d{2}:\d{2}")?;
144 assert_eq!(true, predicate.eval(&date?.format_datetime("%R", &script)));
145
146 let predicate = predicate::str::is_match(r"\d{2}:\d{2}:\d{2}")?;
147 assert_eq!(true, predicate.eval(&date?.format_datetime("%T", &script)));
148 assert_eq!(true, predicate.eval(&date?.format_datetime("%X", &script)));
149 assert_eq!(true, predicate.eval(&date?.format_datetime("%H:%M:%S", &script)));
150
151 let predicate = predicate::str::is_match(r"\d{4}-\d{2}-\d{2}")?;
152 assert_eq!(true, predicate.eval(&date?.format_datetime("%F", &script)));
153 assert_eq!(true, predicate.eval(&date?.format_datetime("%Y-%m-%d", &script)));
154
155 let predicate = predicate::str::is_match(r"\d{4}/\d{2}/\d{2}")?;
156 assert_eq!(true, predicate.eval(&date?.format_datetime("%x", &script)));
157 assert_eq!(true, predicate.eval(&date?.format_datetime("%Y/%m/%d", &script)));
158
159 assert_eq!(date?.format_datetime("%t%n", &script), "\t\n");
160
161 Ok(())
162 }
163
164 #[test]
165 fn test_replace_with_persian_numbers() {
166 assert_eq!(replace_with_persian_numbers("1403-09-26 18:37".to_string()), "۱۴۰۳-۰۹-۲۶ ۱۸:۳۷")
167 }
168
169 #[test]
170 fn test_abbreviated_day_names() {
171 assert_eq!(abbreviated_day_names(&Script::Latin)[0], "Sh");
172 assert_eq!(abbreviated_day_names(&Script::English)[1], "Su");
173 assert_eq!(abbreviated_day_names(&Script::Persian)[2], "دو");
174 }
175
176 #[cfg(fake_date)]
177 #[test]
178 fn test_jalali_date_now() -> Result<(), DateError> {
179 let date = date_now()?;
180
181 assert_eq!(date.year(), 1404);
182 assert_eq!(date.month(), 1);
183 assert_eq!(date.day(), 16);
184 assert_eq!(date.hour(), 22);
185 assert_eq!(date.minute(), 55);
186 assert_eq!(date.second(), 30);
187
188 Ok(())
189 }
190
191 #[cfg(fake_date)]
192 #[test]
193 fn test_date_format() -> Result<(), DateError> {
194 let date = date_now()?;
195 let script = Script::Latin;
196
197 assert_eq!(date.format_datetime("%R", &script), "22:55");
198 assert_eq!(date.format_datetime("%T", &script), "22:55:30");
199 assert_eq!(date.format_datetime("%X", &script), "22:55:30");
200 assert_eq!(date.format_datetime("%H:%M:%S", &script), "22:55:30");
201 assert_eq!(date.format_datetime("%F", &script), "1404-01-16");
202 assert_eq!(date.format_datetime("%e", &script), "16");
203 assert_eq!(date.format_datetime("%t%n", &script), "\t\n");
204 assert_eq!(date.format_datetime("%Y-%m-%d", &script), "1404-01-16");
205 assert_eq!(date.format_datetime("%x", &script), "1404/01/16");
206 assert_eq!(date.format_datetime("%A", &script), "Shanbeh");
207 assert_eq!(date.format_datetime("%B", &script), "Farvardin");
208
209 Ok(())
210 }
211
212 #[cfg(fake_date)]
213 #[test]
214 fn test_date_format_english() -> Result<(), DateError> {
215 let date = date_now()?;
216 let script = Script::English;
217
218 assert_eq!(date.format_datetime("%A", &script), "Saturday");
219 assert_eq!(date.format_datetime("%B", &script), "Farvardin");
220
221 Ok(())
222 }
223
224 #[cfg(fake_date)]
225 #[test]
226 fn test_date_format_persian() -> Result<(), DateError> {
227 let date = date_now()?;
228 let script = Script::Persian;
229
230 assert_eq!(date.format_datetime("%H:%M:%S", &script), "22:55:30");
231 assert_eq!(date.format_datetime("%Y-%m-%d", &script), "1404-01-16");
232 assert_eq!(date.format_datetime("%x", &script), "1404/01/16");
233 assert_eq!(date.format_datetime("%A", &script), "شنبه");
234 assert_eq!(date.format_datetime("%B", &script), "فروردین");
235
236 Ok(())
237 }
238
239 #[cfg(fake_date)]
240 #[test]
241 fn test_date_helper() -> Result<(), DateError> {
242 let date = date_today()?;
243 let script = Script::Latin;
244
245 assert_eq!(date.day_name_in(&script)?, "Shanbeh");
246
247 assert_eq!(date.weekday_from_saturday()?, 0);
248
249 assert_eq!(date.month_name_in(&script), "Farvardin");
250
251 assert_eq!(date.month_first_day()?, 6); let next_month = date.next_month()?;
254 assert_eq!(next_month.month_name_in(&script), "Ordibehesht");
255 assert_eq!(next_month.year(), 1404);
256
257 let prev_month = date.prev_month()?;
258 assert_eq!(prev_month.month_name_in(&script), "Esfand");
259 assert_eq!(prev_month.year(), 1403);
260
261 assert_eq!(date.with_day(31)?.prev_month()?.day(), 30); Ok(())
264 }
265}