ledger_models/fintekkers/wrappers/models/utils/
datetime.rs

1use chrono::{DateTime, Local, NaiveDateTime, TimeZone, Utc};
2use chrono_tz::Tz;
3use prost_types::Timestamp;
4use std::borrow::Borrow;
5use std::fmt;
6use std::str::FromStr;
7use std::string::ParseError;
8
9use crate::fintekkers::models::util::LocalTimestampProto;
10
11pub struct LocalTimestampWrapper {
12    pub proto: LocalTimestampProto,
13}
14
15impl AsRef<LocalTimestampProto> for LocalTimestampWrapper {
16    fn as_ref(&self) -> &LocalTimestampProto {
17        &self.proto
18    }
19}
20
21
22impl LocalTimestampWrapper {
23    pub fn new(proto: LocalTimestampProto) -> Self {
24        LocalTimestampWrapper { proto }
25    }
26
27    pub fn now() -> Self {
28        let time_zone_str = iana_time_zone::get_timezone().unwrap_or_else(|_| {
29            panic!(
30                "{}",
31                String::from(
32                    "Failed to get default timezone from \
33                                         the operating system"
34                )
35            )
36        });
37
38        let now = Local::now().naive_local();
39        Self::from_datetime(now, time_zone_str)
40    }
41
42
43    pub fn from_utc_datetime(now: NaiveDateTime) -> Self {
44        Self::from_datetime(now, "UTC".to_string())
45    }
46
47    pub fn from_datetime(now: NaiveDateTime, time_zone_str: String) -> Self {
48        let seconds = now.timestamp();
49        let nanos = now.timestamp_subsec_nanos();
50
51        let timestamp = Timestamp {
52            seconds,
53            nanos: nanos as i32,
54        };
55
56        LocalTimestampWrapper {
57            proto: LocalTimestampProto {
58                timestamp: Some(timestamp),
59                time_zone: time_zone_str,
60            },
61        }
62    }
63}
64
65
66impl From<&LocalTimestampWrapper> for NaiveDateTime {
67    fn from(wrapper: &LocalTimestampWrapper) -> NaiveDateTime {
68        let timestamp = wrapper.proto.timestamp.as_ref().unwrap().clone();
69
70        let naive_date_time =
71            NaiveDateTime::from_timestamp_opt(timestamp.seconds, timestamp.nanos as u32).unwrap();
72
73        naive_date_time
74    }
75}
76
77impl From<&LocalTimestampWrapper> for DateTime<Tz> {
78    fn from(wrapper: &LocalTimestampWrapper) -> DateTime<Tz> {
79        let timestamp = wrapper.proto.timestamp.as_ref().unwrap().clone();
80
81        let naive_date_time =
82            NaiveDateTime::from_timestamp_opt(timestamp.seconds, timestamp.nanos as u32).unwrap();
83
84        let tz: Tz = wrapper.proto.time_zone.parse().unwrap();
85        let date_timezone = tz.offset_from_utc_datetime(&naive_date_time);
86
87        DateTime::from_utc(naive_date_time, date_timezone)
88    }
89}
90
91impl From<&LocalTimestampWrapper> for DateTime<Utc> {
92    fn from(wrapper: &LocalTimestampWrapper) -> DateTime<Utc> {
93        let timestamp = wrapper.proto.timestamp.as_ref().unwrap().clone();
94
95        Utc.timestamp_opt(timestamp.seconds, timestamp.nanos as u32)
96            .unwrap()
97    }
98}
99impl From<LocalTimestampWrapper> for LocalTimestampProto {
100    fn from(wrapper: LocalTimestampWrapper) -> LocalTimestampProto {
101        wrapper.proto
102    }
103}
104
105impl fmt::Display for LocalTimestampWrapper {
106    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
107        let datetime: DateTime<Tz> = self.into();
108        fmt.write_str(datetime.to_string().borrow()).unwrap();
109        Ok(())
110    }
111}
112
113// From string
114// JSON Mapping
115// In JSON format, the Timestamp type is encoded as a string in the RFC 3339 format. That is, the format is "{year}-{month}-{day}T{hour}:{min}:{sec}\[.{frac_sec}\]Z" where {year} is always expressed using four digits while {month}, {day}, {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone is required. A proto3 JSON serializer should always use UTC (as indicated by "Z") when printing the Timestamp type and a proto3 JSON parser should be able to accept both UTC and other timezones (as indicated by an offset).
116// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past 01:30 UTC on January 15, 2017.
117// In JavaScript, one can convert a Date object to this format using the standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString ) method. In Python, a standard datetime.datetime object can be converted to this format using [strftime](https://docs.python.org/2/library/time.html#time.strftime ) with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use the Joda Time's [ISODateTimeFormat.dateTime()](http://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime%2D%2D ) to obtain a formatter capable of generating timestamps in this format.
118
119impl FromStr for LocalTimestampWrapper {
120    type Err = ParseError;
121
122    fn from_str(s: &str) -> Result<Self, Self::Err> {
123        let datetime: DateTime<Utc> = DateTime::parse_from_rfc3339(s)
124            .map(|dt| dt.with_timezone(&Utc))
125            .expect("");
126
127        let time_zone_str = iana_time_zone::get_timezone().unwrap_or_else(|_| {
128            panic!(
129                "{}",
130                String::from(
131                    "Failed to get default timezone from \
132                                         the operating system"
133                )
134            )
135        });
136
137        Ok(LocalTimestampWrapper {
138            proto: LocalTimestampProto {
139                timestamp: Some(create_timestamp_from_datetime(datetime)),
140                time_zone: time_zone_str,
141            },
142        })
143    }
144}
145
146fn create_timestamp_from_datetime(now: DateTime<Utc>) -> Timestamp {
147    let seconds = now.timestamp();
148    let nanos = now.timestamp_subsec_nanos() as i32;
149    Timestamp { seconds, nanos }
150}
151#[cfg(test)]
152mod test {
153    use super::*;
154
155    #[test]
156    fn test_proto_to_date() {
157        let input = LocalTimestampWrapper {
158            proto: LocalTimestampProto {
159                timestamp: Some(Timestamp {
160                    seconds: 1,
161                    nanos: 0,
162                }),
163                time_zone: "America/New_York".to_string(),
164            },
165        };
166
167        let output: DateTime<Tz> = input.borrow().into();
168
169        //Timstamp of 1 second is 1970, Jan 1st at midnight, then when converted into NY timezone
170        //from UTC it will be 5 hours earlier...
171        assert_eq!(output.to_string(), "1969-12-31 19:00:01 EST");
172    }
173
174    #[test]
175    fn test_date_from_naive_date() {
176        let date_time: DateTime<Utc> = Utc::now();
177        let wrapper = LocalTimestampWrapper::from_utc_datetime(date_time.naive_utc());
178
179        //Check no timezone specified uses UTC
180        let wrapper_seconds = wrapper.proto.timestamp.unwrap().seconds;
181        assert_eq!(date_time.timestamp(), wrapper_seconds);
182        assert_eq!(date_time.timezone().to_string(), wrapper.proto.time_zone);
183
184        //Check explicit UTC timezone specified uses UTC
185        let utc_wrapper = LocalTimestampWrapper::from_datetime(date_time.naive_utc(), "UTC".to_string());
186
187        let utc_wrapper_seconds = utc_wrapper.proto.timestamp.unwrap().seconds;
188        assert_eq!(date_time.timestamp(), utc_wrapper_seconds);
189        assert_eq!(
190            date_time.timezone().to_string(),
191            utc_wrapper.proto.time_zone
192        );
193
194        //Check explicit NY timezone specified doesn't use UTC
195        let ny_wrapper =
196            LocalTimestampWrapper::from_datetime(date_time.naive_local(), "America/New_York".to_string());
197
198        let ny_wrapper_seconds = ny_wrapper.proto.timestamp.unwrap().seconds;
199        assert_eq!(date_time.timestamp(), ny_wrapper_seconds);
200        assert_eq!("America/New_York".to_string(), ny_wrapper.proto.time_zone);
201    }
202
203    #[test]
204    fn test_date_from_string() {
205        let date = LocalTimestampWrapper::from_str("2023-03-17T12:34:56Z").unwrap();
206
207        let option = date.proto.timestamp.unwrap();
208        assert_eq!(option.seconds, 1679056496);
209    }
210
211
212    #[test]
213    fn test_timezones() {
214        let now = LocalTimestampWrapper::now();
215        let string = now.to_string();
216        println!("{}", string);
217    }
218
219}