1use time::{Date, Month, OffsetDateTime};
4
5pub fn clean_fa(s: &str) -> String {
8 s.replace('\u{200B}', "") .replace('\u{200C}', " ") .replace(['\u{200D}', '\u{FEFF}'], "") .replace('\u{0643}', "\u{06A9}") .replace('\u{064A}', "\u{06CC}") }
14
15pub fn date_to_str(d: Date) -> String {
17 format!("{:04}{:02}{:02}", d.year(), d.month() as u8, d.day())
18}
19
20pub fn today() -> Date {
22 OffsetDateTime::now_local()
23 .unwrap_or_else(|_| OffsetDateTime::now_utc())
24 .date()
25}
26
27fn month_from_u8(m: u8) -> Month {
28 Month::try_from(m).unwrap_or(Month::January)
29}
30
31pub fn str_to_date(s: &str) -> Option<Date> {
33 if s.len() < 8 {
34 return None;
35 }
36 let y: i32 = s[0..4].parse().ok()?;
37 let m: u8 = s[4..6].parse().ok()?;
38 let d: u8 = s[6..8].parse().ok()?;
39 Date::from_calendar_date(y, month_from_u8(m), d).ok()
40}
41
42pub fn greg_to_shamsi(s: &str) -> String {
45 if s.len() < 8 {
46 return s.to_string();
47 }
48 let y: i32 = s[0..4].parse().unwrap_or(0);
49 let m: i32 = s[4..6].parse().unwrap_or(0);
50 let d: i32 = s[6..8].parse().unwrap_or(0);
51 match ptime::from_gregorian_date(y, m - 1, d) {
52 Some(pt) => format!("{:04}{:02}{:02}", pt.tm_year, pt.tm_mon + 1, pt.tm_mday),
53 None => s.to_string(),
54 }
55}
56
57pub fn shamsi_to_greg(s: &str) -> String {
59 if s.len() < 8 {
60 return s.to_string();
61 }
62 let y: i32 = s[0..4].parse().unwrap_or(0);
63 let m: i32 = s[4..6].parse().unwrap_or(0);
64 let d: i32 = s[6..8].parse().unwrap_or(0);
65 match ptime::from_persian_date(y, m - 1, d) {
66 Some(pt) => {
67 let g = pt.to_gregorian();
68 format!("{:04}{:02}{:02}", g.tm_year + 1900, g.tm_mon + 1, g.tm_mday)
69 }
70 None => s.to_string(),
71 }
72}
73
74pub fn day_diff(s1: &str, s2: &str) -> i64 {
76 let (d1, d2) = match (str_to_date(s1), str_to_date(s2)) {
77 (Some(a), Some(b)) => (a, b),
78 _ => return 0,
79 };
80 (d2 - d1).whole_days().abs()
81}
82
83pub fn split_chunks<T: Clone>(items: &[T], size: usize) -> Vec<Vec<T>> {
85 if size == 0 {
86 return vec![];
87 }
88 items.chunks(size).map(|c| c.to_vec()).collect()
89}
90
91fn js_weekday(d: Date) -> u8 {
93 d.weekday().number_days_from_sunday()
95}
96
97const UPDATE_INTERVAL: i64 = 1;
98const TRADING_SESSION_END_HOUR: u8 = 16;
99
100pub fn should_update(deven: &str, last_possible_deven: &str) -> bool {
102 if deven.is_empty() || deven == "0" {
103 return true;
104 }
105 let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
106 let today_d = now.date();
107 let today_deven = date_to_str(today_d);
108 let days_passed = day_diff(last_possible_deven, deven);
109 let in_weekend = matches!(js_weekday(today_d), 4 | 5); let last_update_weekday = str_to_date(last_possible_deven)
111 .map(js_weekday)
112 .unwrap_or(0);
113
114 days_passed >= UPDATE_INTERVAL
115 && (if today_deven == last_possible_deven {
116 now.hour() > TRADING_SESSION_END_HOUR
117 } else {
118 true
119 })
120 && !(in_weekend && last_update_weekday != 3 && days_passed <= 3)
121}
122
123pub fn shamsi_month_key(greg: &str) -> Option<String> {
132 let sh = greg_to_shamsi(greg);
133 if sh.len() < 6 {
134 return None;
135 }
136 if sh == greg {
139 return None;
140 }
141
142 let month_start_shamsi = format!("{}01", &sh[0..6]);
144 let month_start_greg = shamsi_to_greg(&month_start_shamsi);
145
146 if month_start_greg.len() < 8 || month_start_greg == month_start_shamsi {
147 return None;
148 }
149
150 Some(month_start_greg)
151}
152
153pub fn shamsi_week_key(greg: &str) -> Option<String> {
160 let date = str_to_date(greg)?;
161 let days_since_saturday = ((date.weekday().number_days_from_sunday() + 1) % 7) as i64;
165 let week_start = date - time::Duration::days(days_since_saturday);
166 Some(date_to_str(week_start))
167}