thisweek_core/
week.rs

1/* Week */
2
3// https://en.wikipedia.org/wiki/Unix_time
4// https://en.wikipedia.org/wiki/January_1970#January_1,_1970_(Thursday)
5// https://en.wikipedia.org/wiki/Leap_second
6// https://www.time.ir/
7
8// use std::time;
9
10use crate::calendar::Calendar;
11use crate::config;
12use crate::db_sqlite;
13use crate::language::Language;
14use crate::models::*;
15use crate::ordering::Ordering;
16use crate::ordering::Result;
17use crate::prelude::Result as AppResult;
18use crate::today;
19use crate::week_info::WeekInfo;
20use crate::weekdays::WeekDaysUnixOffset;
21use crate::weekdays::SEVEN_DAY_WEEK_SIZE;
22use serde::Serialize;
23
24#[derive(Debug, Clone, Default)]
25pub struct Week {
26    pub reference_day: i32,
27    pub start_day: i32,
28    pub middle_day: i32,
29    pub end_day: i32,
30    pub items: Vec<Item>,
31    // for frontend view only
32    pub week_view: WeekView,
33}
34
35#[derive(Debug, Serialize, Clone, Default)]
36pub struct WeekView {
37    pub week_info_main: WeekInfo,
38    pub week_info_aux: Option<WeekInfo>,
39    pub items: Vec<ItemView>,
40}
41
42impl Week {
43    pub fn new() -> Self {
44        let mut week = Week::default();
45        let _ = week.current();
46        week
47    }
48
49    // January 1, 1970 was Thursday
50    // Thu, Fri, Sat, Sun, Mon, Tue, Wed,
51    // 0  , 1  , 2  , 3  , 4  , 5  , 6  ,
52    // ex: persian 7 day weeks starts from saturday
53    // day_offset = WEEKDAY_UNIX_OFFSET_SAT // 2
54    // week_size = SEVEN_DAY_WEEK_SIZE // 7
55    fn calculate_week_start_middle_end_unix_day(
56        unix_day: i32,
57        day_offset: i32,
58        week_size: i32,
59    ) -> (i32, i32, i32) {
60        let start = ((unix_day - day_offset) / week_size) * week_size + day_offset;
61        let middle =
62            ((unix_day - day_offset) / week_size) * week_size + day_offset + (week_size / 2);
63        let end = ((unix_day - day_offset) / week_size) * week_size + day_offset + week_size - 1;
64        (start, middle, end)
65    }
66
67    pub fn update(&mut self) -> AppResult<()> {
68        // update general week start/middle/end unix days
69        let start_week_day: WeekDaysUnixOffset =
70            config::get_config().main_calendar_start_weekday.into();
71        let start_week_day_offset: i32 = start_week_day as i32;
72        let (start_day, middle_day, end_day) = Self::calculate_week_start_middle_end_unix_day(
73            self.reference_day,
74            start_week_day_offset,
75            SEVEN_DAY_WEEK_SIZE,
76        );
77        self.start_day = start_day;
78        self.middle_day = middle_day;
79        self.end_day = end_day;
80
81        // update items
82        let items = db_sqlite::read_items_between_days(self.start_day, self.end_day, true)?;
83        // todo: exclude the objectives, include the ones that are fixed date
84        self.items = items;
85        self.check_and_fix_ordering();
86
87        // update view items
88        let today = today::get_unix_day();
89        let main_cal: Calendar = config::get_config().main_calendar_type.into();
90        let main_cal_lang = config::get_config().main_calendar_language.into();
91        self.week_view.week_info_main = WeekInfo::from_unix_start_end_days(
92            self.start_day,
93            self.end_day,
94            today,
95            main_cal,
96            main_cal_lang,
97        )?;
98        let aux_cal: Option<Calendar> = config::get_config()
99            .secondary_calendar_type
100            .map(|s| s.into());
101        self.week_view.week_info_aux = aux_cal.map(|cal| {
102            let aux_language: Language = config::get_config()
103                .secondary_calendar_language
104                .unwrap_or_default()
105                .into();
106            WeekInfo::from_unix_start_end_days(
107                self.start_day,
108                self.end_day,
109                today,
110                cal,
111                aux_language,
112            )
113            .unwrap_or_default()
114        });
115        self.week_view.items = self.items.iter().map(ItemView::from).collect();
116        Ok(())
117    }
118
119    pub fn get_view(&self) -> WeekView {
120        self.week_view.clone()
121    }
122
123    #[allow(clippy::should_implement_trait)]
124    pub fn next(&mut self) -> AppResult<()> {
125        self.reference_day += SEVEN_DAY_WEEK_SIZE;
126        self.update()
127    }
128
129    pub fn previous(&mut self) -> AppResult<()> {
130        self.reference_day -= SEVEN_DAY_WEEK_SIZE;
131        self.update()
132    }
133
134    pub fn current(&mut self) -> AppResult<()> {
135        self.reference_day = today::get_unix_day();
136        // println!(
137        //     "setting week to current date. reference_day: {}",
138        //     self.reference_day
139        // );
140        self.update()
141    }
142
143    pub fn add_new_item(
144        &mut self,
145        kind: i32,
146        text: String,
147        after_id: Option<i32>,
148    ) -> AppResult<i32> {
149        let main_cal: Calendar = config::get_config().main_calendar_type.into();
150        let calendar: i32 = main_cal.into();
151        let ordering_key: String = self.get_new_ordering_key(after_id);
152        let new_item = NewItem::new(
153            calendar,
154            None, //year,
155            None, //season,
156            None, //month,
157            self.middle_day,
158            kind,
159            text,
160            ordering_key,
161        );
162        db_sqlite::create_item(&new_item)
163    }
164
165    pub fn move_item_to_other_time_period_offset(&mut self, id: i32, offset: i32) -> Result<usize> {
166        if let Some(pos) = self.items.iter().position(|item| item.id == id) {
167            let mut item = self.items[pos].clone();
168            item.day += SEVEN_DAY_WEEK_SIZE * offset;
169            item.order_in_week = None;
170            let result = db_sqlite::update_item(&item);
171            let _ = self.update();
172            result
173        } else {
174            let _ = self.update();
175            Err("id not in list!".into())
176        }
177    }
178}
179
180impl Ordering for Week {
181    fn get_keys(&self) -> Vec<Option<String>> {
182        self.items.iter().map(|i| i.order_in_week.clone()).collect()
183    }
184
185    // fn get_ordering_key_of_posision(&self, i: usize) -> Result<Option<String>> {
186    //     Ok(self.items.get(i).ok_or("invalid position".to_string())?.order_in_week.clone())
187    // }
188
189    fn set_ordering_key_of_posision(&mut self, i: usize, key: Option<String>) -> Result<()> {
190        self.items
191            .get_mut(i)
192            .ok_or("invalid pos".to_string())?
193            .order_in_week = key;
194        Ok(())
195    }
196
197    // fn get_posision_of_id(&self, id: i32) -> Result<usize> {
198    //     self.items.iter().position(|item| item.id == id)
199    // }
200
201    fn get_ordering_key_of_id(&self, id: i32) -> Option<Option<String>> {
202        let pos = self.items.iter().position(|item| item.id == id)?;
203        Some(self.items.get(pos).unwrap().order_in_week.clone())
204    }
205
206    fn new_ordering_finished(&self) {
207        let _ = db_sqlite::update_items(&self.items);
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use crate::time;
214    use crate::week::Week;
215    use crate::weekdays::{WeekDaysUnixOffset, SEVEN_DAY_WEEK_SIZE};
216    use chrono::{DateTime, Local};
217
218    #[test]
219    fn test_week_middle_day_ref() {
220        let gregorian_dates_and_reference = vec![
221            // persian 1403-04-22
222            ("2024-07-12 23:22:11 +03:30", 19913),
223            ("2024-07-12 23:59:36 +03:30", 19913),
224            ("2024-07-12 23:59:59 +03:30", 19913),
225            // persian 1403-04-23
226            ("2024-07-13 00:00:00 +03:30", 19920),
227            ("2024-07-13 00:00:01 +03:30", 19920),
228            ("2024-07-13 00:00:11 +03:30", 19920),
229            ("2024-07-13 01:01:01 +03:30", 19920),
230            ("2024-07-13 12:00:00 +03:30", 19920),
231            ("2024-07-14 12:00:00 +03:30", 19920),
232            ("2024-07-15 00:00:00 +03:30", 19920),
233            ("2024-07-16 23:00:00 +03:30", 19920),
234            ("2024-07-17 23:23:00 +03:30", 19920),
235            ("2024-07-18 23:23:23 +03:30", 19920),
236            ("2024-07-18 23:59:23 +03:30", 19920),
237            ("2024-07-19 00:00:00 +03:30", 19920),
238            ("2024-07-19 02:00:00 +03:30", 19920),
239            ("2024-07-19 05:00:00 +03:30", 19920),
240            ("2024-07-19 18:00:00 +03:30", 19920),
241            ("2024-07-19 23:00:00 +03:30", 19920),
242            ("2024-07-19 23:59:59 +03:30", 19920),
243            // persian 1403-04-30
244            ("2024-07-20 00:00:00 +03:30", 19927),
245            ("2024-07-20 00:00:01 +03:30", 19927),
246            ("2024-07-20 00:01:01 +03:30", 19927),
247            ("2024-07-20 01:00:01 +03:30", 19927),
248            ("2024-07-20 03:00:00 +03:30", 19927),
249            ("2024-07-20 04:00:00 +03:30", 19927),
250        ];
251        assert!(check_gregorian_dates_and_reference(
252            gregorian_dates_and_reference
253        ));
254    }
255
256    fn check_gregorian_dates_and_reference(dates_and_ref: Vec<(&str, i32)>) -> bool {
257        println!("----");
258        for (date_string, expected_middle_day) in dates_and_ref {
259            let dt = date_string.parse::<DateTime<Local>>().unwrap();
260            let unix_day = time::get_unix_day_from_local_datetime(dt);
261            let (s, m, e) = Week::calculate_week_start_middle_end_unix_day(
262                unix_day,
263                WeekDaysUnixOffset::Sat as i32,
264                SEVEN_DAY_WEEK_SIZE,
265            );
266            println!(
267                "date: {}, start_day: {}, middle_day: {}, end_day: {}",
268                dt, s, m, e
269            );
270            if expected_middle_day != m {
271                return false;
272            }
273        }
274        true
275    }
276}