smartcalc/compiler/
date.rs

1/*
2 * smartcalc v1.0.8
3 * Copyright (c) Erhan BARIS (Ruslan Ognyanov Asenov)
4 * Licensed under the GNU General Public License v2.0.
5 */
6
7use core::any::{Any, TypeId};
8use alloc::rc::Rc;
9use alloc::string::ToString;
10use alloc::string::String;
11use chrono::{Datelike, Duration, NaiveDate, Utc, TimeZone};
12use crate::session::Session;
13use crate::compiler::duration::DurationItem;
14use crate::config::SmartCalcConfig;
15use crate::formatter::{MONTH, YEAR, get_month_info, left_padding, uppercase_first_letter};
16use crate::types::{TokenType, TimeOffset};
17
18use super::{DataItem, OperationType, UnaryType};
19
20#[derive(Debug)]
21
22pub struct DateItem(pub NaiveDate, pub TimeOffset);
23
24impl DateItem {
25    pub fn get_date(&self) -> NaiveDate {
26        self.0
27    }
28    
29    pub fn get_tz(&self) -> TimeOffset {
30        self.1.clone()
31    }
32    
33    fn get_month_from_duration(&self, duration: Duration) -> i64 {
34        duration.num_seconds().abs() / MONTH
35    }
36
37    fn get_year_from_duration(&self, duration: Duration) -> i64 {
38        duration.num_seconds().abs() / YEAR
39    }
40}
41
42impl DataItem for DateItem {
43    fn as_token_type(&self) -> TokenType {
44        TokenType::Date(self.0, self.1.clone())
45    }
46    fn is_same(&self, other: &dyn Any) -> bool {
47        match other.downcast_ref::<NaiveDate>() {
48            Some(l_value) => l_value == &self.0,
49            None => false
50        }
51    }
52    fn as_any(&self) -> &dyn Any { self }
53    
54    fn calculate(&self, _: &SmartCalcConfig, _: bool, other: &dyn DataItem, operation_type: OperationType) -> Option<Rc<dyn DataItem>> {
55        /* If both item is money and current money is on left side, skip calculation */
56        if other.type_name() != "DURATION" {
57            return None;
58        }
59
60        let mut date = self.0;
61        let mut duration = other.as_any().downcast_ref::<DurationItem>()?.get_duration();
62
63        match operation_type {
64            OperationType::Add => {
65                match self.get_year_from_duration(duration) {
66                    0 => (),
67                    n => {
68                        let years_diff = date.year() + n as i32;
69                        date     = NaiveDate::from_ymd(years_diff as i32, date.month() as u32, date.day());
70                        duration = Duration::seconds(duration.num_seconds() - (YEAR * n))
71                    }
72                };
73
74                match self.get_month_from_duration(duration) {
75                    0 => (),
76                    n => {
77                        let years_diff = (date.month() + n as u32) / 12;
78                        let month = (date.month() + n as u32) % 12;
79                        date     = NaiveDate::from_ymd(date.year() + years_diff as i32, month as u32, date.day());
80                        duration = Duration::seconds(duration.num_seconds() - (MONTH * n))
81                    }
82                };
83                Some(Rc::new(DateItem(date + duration, self.1.clone())))
84            },
85
86            OperationType::Sub => {
87                match self.get_year_from_duration(duration) {
88                    0 => (),
89                    n => {
90                        let years_diff = date.year() - n as i32;
91                        date     = NaiveDate::from_ymd(years_diff as i32, date.month() as u32, date.day());
92                        duration = Duration::seconds(duration.num_seconds() - (YEAR * n))
93                    }
94                };
95
96                match self.get_month_from_duration(duration) {
97                    0 => (),
98                    n => {
99                        let years = date.year() - (n as i32 / 12);
100                        let mut months = date.month() as i32 - (n as i32 % 12);
101                        if months < 0 {
102                            months += 12;
103                        }
104
105                        date = NaiveDate::from_ymd(years as i32, months as u32, date.day());
106                        duration = Duration::seconds(duration.num_seconds() - (MONTH * n))
107                    }
108                };
109                Some(Rc::new(DateItem(date - duration, self.1.clone())))
110            },
111            _ => None
112        }
113    }
114    
115    fn get_number(&self, _: &dyn DataItem) -> f64 {
116       self.get_underlying_number()
117    }
118    
119    fn get_underlying_number(&self) -> f64 { 0.0 }
120    fn type_name(&self) -> &'static str { "DATE" }
121    fn type_id(&self) -> TypeId { TypeId::of::<DateItem>() }
122    fn print(&self, config: &SmartCalcConfig, session: &Session) -> String {
123
124        let format = match config.format.get( &session.get_language()) {
125            Some(formats) => formats,
126            _ => match config.format.get( "en") {
127                Some(formats) => formats,
128                _ => return "".to_string()
129            }
130        };
131        
132        let date_format = match self.0.year() == Utc::now().date().year() {
133            true => format.date.get("current_year"),
134            false => format.date.get("full_date")
135        };
136
137        let tz_offset = chrono::FixedOffset::east(self.1.offset * 60);
138        let datetime = tz_offset.from_utc_date(&self.0);
139        
140        match date_format {
141            Some(data) => {
142                match get_month_info(config, &format.language, datetime.month() as u8) {
143                    Some(month_info) => data.clone()
144                        .replace("{day}", &datetime.day().to_string())
145                        .replace("{month}", &datetime.month().to_string())
146                        .replace("{day_pad}", &left_padding(datetime.day().into(), 2))
147                        .replace("{month_pad}", &left_padding(datetime.month().into(), 2))
148                        .replace("{month_long}", &uppercase_first_letter(&month_info.long))
149                        .replace("{month_short}", &uppercase_first_letter(&month_info.short))
150                        .replace("{year}", &datetime.year().to_string())
151                        .replace("{timezone}", &self.1.name),
152                    None => datetime.to_string()
153                }
154            },
155            None => datetime.to_string()
156        }
157    }
158    fn unary(&self, _: UnaryType) -> Rc<dyn DataItem> {
159        Rc::new(Self(self.0, self.1.clone()))
160    }
161}
162
163
164#[cfg(test)]
165#[test]
166fn date_test() {
167    use crate::compiler::date::DateItem;
168    use crate::compiler::duration::DurationItem;
169    use crate::config::SmartCalcConfig;
170    let config = SmartCalcConfig::default();
171    let session = Session::default();
172
173    assert_eq!(DateItem(NaiveDate::from_ymd(2020, 1, 1), config.get_time_offset()).print(&config, &session), "1 Jan 2020".to_string());
174
175    let left = DateItem(NaiveDate::from_ymd(2020, 1, 1), config.get_time_offset());
176    let right = DateItem(NaiveDate::from_ymd(2020, 1, 1), config.get_time_offset());
177    let result = left.calculate(&config, true, &right, OperationType::Sub);
178    
179    assert!(result.is_none());
180
181    let left = DateItem(NaiveDate::from_ymd(2020, 1, 1), config.get_time_offset());
182    let right = DurationItem(Duration::hours(24 * 20));
183    let result = left.calculate(&config, true, &right, OperationType::Add);
184    
185    assert!(result.is_some());
186    assert_eq!(result.unwrap().print(&config, &session), "21 Jan 2020".to_string());
187}