1use chrono::{NaiveDate, NaiveTime};
6use chrono_tz::Tz;
7use raystack_core::{FromHaysonError, Hayson};
8use serde_json::{json, Value};
9use std::convert::From;
10
11const KIND: &str = "_kind";
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct Date(NaiveDate);
16
17impl Date {
18 pub fn new(naive_date: NaiveDate) -> Self {
19 Date(naive_date)
20 }
21
22 pub fn naive_date(&self) -> &NaiveDate {
23 &self.0
24 }
25
26 pub fn into_naive_date(self) -> NaiveDate {
27 self.0
28 }
29}
30
31impl Hayson for Date {
32 fn from_hayson(value: &Value) -> Result<Self, FromHaysonError> {
33 match &value {
34 Value::Object(obj) => {
35 if let Some(kind_err) = hayson_check_kind("date", value) {
36 return Err(kind_err);
37 }
38 let val = obj.get("val");
39
40 if val.is_none() {
41 return hayson_error("Date val is missing");
42 }
43
44 let val = val.unwrap().as_str();
45
46 if val.is_none() {
47 return hayson_error("Date val is not a string");
48 }
49
50 let val = val.unwrap();
51
52 match val.parse() {
53 Ok(naive_date) => Ok(Date::new(naive_date)),
54 Err(_) => hayson_error(
55 "Date val string could not be parsed as a NaiveDate",
56 ),
57 }
58 }
59 _ => hayson_error("Date JSON value must be an object"),
60 }
61 }
62
63 fn to_hayson(&self) -> Value {
64 json!({
65 KIND: "date",
66 "val": self.naive_date().to_string(),
67 })
68 }
69}
70
71impl From<NaiveDate> for Date {
72 fn from(d: NaiveDate) -> Self {
73 Self::new(d)
74 }
75}
76
77#[derive(Clone, Debug, Eq, PartialEq)]
79pub struct Time(NaiveTime);
80
81impl Time {
82 pub fn new(naive_time: NaiveTime) -> Self {
83 Time(naive_time)
84 }
85
86 pub fn naive_time(&self) -> &NaiveTime {
87 &self.0
88 }
89
90 pub fn into_naive_time(self) -> NaiveTime {
91 self.0
92 }
93}
94
95impl Hayson for Time {
96 fn from_hayson(value: &Value) -> Result<Self, FromHaysonError> {
97 match &value {
98 Value::Object(obj) => {
99 if let Some(kind_err) = hayson_check_kind("time", value) {
100 return Err(kind_err);
101 }
102 let val = obj.get("val");
103
104 if val.is_none() {
105 return hayson_error("Time val is missing");
106 }
107
108 let val = val.unwrap().as_str();
109
110 if val.is_none() {
111 return hayson_error("Time val is not a string");
112 }
113
114 let val = val.unwrap();
115
116 match val.parse() {
117 Ok(naive_time) => Ok(Time::new(naive_time)),
118 Err(_) => hayson_error(
119 "Time val string could not be parsed as a NaiveTime",
120 ),
121 }
122 }
123 _ => hayson_error("Time JSON value must be an object"),
124 }
125 }
126
127 fn to_hayson(&self) -> Value {
128 json!({
129 KIND: "time",
130 "val": self.naive_time().to_string(),
131 })
132 }
133}
134
135impl From<NaiveTime> for Time {
136 fn from(t: NaiveTime) -> Self {
137 Self::new(t)
138 }
139}
140
141#[derive(Clone, Debug, Eq, PartialEq)]
143pub struct DateTime {
144 date_time: chrono::DateTime<Tz>,
145}
146
147impl DateTime {
148 pub fn new(date_time: chrono::DateTime<Tz>) -> Self {
149 Self { date_time }
150 }
151
152 pub fn date_time(&self) -> &chrono::DateTime<Tz> {
153 &self.date_time
154 }
155
156 pub fn into_date_time(self) -> chrono::DateTime<Tz> {
157 self.date_time
158 }
159 pub fn time_zone(&self) -> &str {
161 self.date_time.timezone().name()
162 }
163
164 pub fn short_time_zone(&self) -> &str {
168 crate::tz::time_zone_name_to_short_name(self.time_zone())
169 }
170}
171
172impl Hayson for DateTime {
173 fn from_hayson(value: &Value) -> Result<Self, FromHaysonError> {
174 let default_tz = "GMT";
177
178 match &value {
179 Value::Object(obj) => {
180 if let Some(kind_err) = hayson_check_kind("dateTime", value) {
181 return Err(kind_err);
182 }
183
184 let tz_value = obj.get("tz");
185 let mut tz_str = default_tz.to_owned();
186
187 if let Some(value) = tz_value {
188 match value {
189 Value::Null => {
190 tz_str = default_tz.to_owned();
191 }
192 Value::String(tz_string) => {
193 tz_str = tz_string.clone();
194 }
195 _ => {
196 return hayson_error(
197 "DateTime tz is not a null or a string",
198 )
199 }
200 }
201 }
202
203 let dt = obj.get("val");
204
205 if dt.is_none() {
206 return hayson_error("DateTime val is missing");
207 }
208
209 let dt = dt.unwrap().as_str();
210
211 if dt.is_none() {
212 return hayson_error("DateTime val is not a string");
213 }
214
215 let dt = dt.unwrap();
216
217 match chrono::DateTime::parse_from_rfc3339(dt) {
218 Ok(dt) => {
219 let tz = crate::skyspark_tz_string_to_tz(&tz_str);
220 if let Some(tz) = tz {
221 let dt = dt.with_timezone(&tz);
222 Ok(DateTime::new(dt))
223 } else {
224 hayson_error(format!("DateTime tz '{}' has no matching chrono_tz time zone", tz_str))
225 }
226 }
227 Err(_) => hayson_error(
228 "Time val string could not be parsed as a NaiveTime",
229 ),
230 }
231 }
232 _ => hayson_error("Time JSON value must be an object"),
233 }
234 }
235
236 fn to_hayson(&self) -> Value {
237 json!({
238 KIND: "dateTime",
239 "val": self.date_time().to_rfc3339(),
240 "tz": self.short_time_zone(),
241 })
242 }
243}
244
245impl From<chrono::DateTime<Tz>> for DateTime {
246 fn from(dt: chrono::DateTime<Tz>) -> Self {
247 Self::new(dt)
248 }
249}
250
251fn hayson_error<T, M>(message: M) -> Result<T, FromHaysonError>
252where
253 M: AsRef<str>,
254{
255 Err(FromHaysonError::new(message.as_ref().to_owned()))
256}
257
258fn hayson_error_opt<M>(message: M) -> Option<FromHaysonError>
259where
260 M: AsRef<str>,
261{
262 Some(FromHaysonError::new(message.as_ref().to_owned()))
263}
264
265fn hayson_check_kind(
266 target_kind: &str,
267 value: &Value,
268) -> Option<FromHaysonError> {
269 match value.get(KIND) {
270 Some(kind) => match kind {
271 Value::String(kind) => {
272 if kind == target_kind {
273 None
274 } else {
275 hayson_error_opt(format!(
276 "Expected '{}' = {} but found {}",
277 KIND, kind, kind
278 ))
279 }
280 }
281 _ => hayson_error_opt(format!("'{}' key is not a string", KIND)),
282 },
283 None => hayson_error_opt(format!("Missing '{}' key", KIND)),
284 }
285}
286
287#[cfg(test)]
288mod test {
289 use crate::{Date, DateTime, Time};
290 use chrono::{NaiveDate, NaiveTime};
291 use chrono_tz::Tz;
292 use raystack_core::Hayson;
293
294 #[test]
295 fn serde_date_works() {
296 let naive_date = NaiveDate::from_ymd(2021, 1, 1);
297 let x = Date::new(naive_date);
298 let value = x.to_hayson();
299 let deserialized = Date::from_hayson(&value).unwrap();
300 assert_eq!(x, deserialized);
301 }
302
303 #[test]
304 fn serde_time_works() {
305 let naive_time = NaiveTime::from_hms(2, 15, 59);
306 let x = Time::new(naive_time);
307 let value = x.to_hayson();
308 let deserialized = Time::from_hayson(&value).unwrap();
309 assert_eq!(x, deserialized);
310 }
311
312 #[test]
313 fn serde_date_time_works() {
314 let dt =
315 chrono::DateTime::parse_from_rfc3339("2021-01-01T18:30:09.453Z")
316 .unwrap()
317 .with_timezone(&Tz::GMT);
318 let x = DateTime::new(dt);
319 let value = x.to_hayson();
320 let deserialized = DateTime::from_hayson(&value).unwrap();
321 assert_eq!(x, deserialized);
322 }
323
324 #[test]
325 fn serde_date_time_with_one_slash_tz_works() {
326 let dt =
327 chrono::DateTime::parse_from_rfc3339("2021-01-01T18:30:09.453Z")
328 .unwrap()
329 .with_timezone(&Tz::Australia__Sydney);
330 let x = DateTime::new(dt);
331 let value = x.to_hayson();
332 let deserialized = DateTime::from_hayson(&value).unwrap();
333 assert_eq!(x, deserialized);
334 }
335
336 #[test]
337 fn serde_date_time_with_multiple_slashes_tz_works() {
338 let dt =
339 chrono::DateTime::parse_from_rfc3339("2021-01-01T18:30:09.453Z")
340 .unwrap()
341 .with_timezone(&Tz::America__North_Dakota__Beulah);
342 let x = DateTime::new(dt);
343 let value = x.to_hayson();
344 let deserialized = DateTime::from_hayson(&value).unwrap();
345 assert_eq!(x, deserialized);
346 }
347
348 #[test]
349 fn short_time_zone_works() {
350 let dt: DateTime =
351 chrono::DateTime::parse_from_rfc3339("2021-01-01T18:30:09.453Z")
352 .unwrap()
353 .with_timezone(&Tz::America__North_Dakota__Beulah)
354 .into();
355 assert_eq!(dt.short_time_zone(), "Beulah");
356
357 let dt: DateTime =
358 chrono::DateTime::parse_from_rfc3339("2021-01-01T18:30:09.453Z")
359 .unwrap()
360 .with_timezone(&Tz::GMT)
361 .into();
362 assert_eq!(dt.short_time_zone(), "GMT");
363
364 let dt: DateTime =
365 chrono::DateTime::parse_from_rfc3339("2021-01-01T18:30:09.453Z")
366 .unwrap()
367 .with_timezone(&Tz::Australia__Sydney)
368 .into();
369 assert_eq!(dt.short_time_zone(), "Sydney");
370 }
371}