yamlette/model/yaml/
timestamp.rs

1extern crate fraction;
2extern crate num;
3extern crate skimmer;
4
5use self::fraction::{BigFraction, Fraction};
6use self::num::BigUint;
7
8use crate::model::{EncodedString, Model, Node, Renderer, Rope, Tagged, TaggedValue};
9
10use crate::model::yaml::float::FloatValue;
11
12use std::any::Any;
13use std::borrow::Cow;
14use std::i32;
15use std::iter::Iterator;
16
17pub static TAG: &'static str = "tag:yaml.org,2002:timestamp";
18
19#[derive(Clone, Copy)]
20pub struct Timestamp;
21
22impl Timestamp {
23    pub fn get_tag() -> Cow<'static, str> {
24        Cow::from(TAG)
25    }
26
27    fn parse_figure(&self, ptr: &mut usize, value: &[u8]) -> Option<i64> {
28        let mut figure: Option<i64> = None;
29
30        loop {
31            match value.get(*ptr).map(|b| *b) {
32                Some(val @ b'0'..=b'9') => {
33                    let val = val - b'0';
34
35                    figure = if let Some(nval) =
36                        (if figure.is_some() { figure.unwrap() } else { 0 }).checked_mul(10)
37                    {
38                        if let Some(nval) = nval.checked_add(val as i64) {
39                            *ptr += 1;
40                            Some(nval)
41                        } else {
42                            return None;
43                        }
44                    } else {
45                        return None;
46                    };
47                }
48                _ => break,
49            }
50        }
51
52        figure
53    }
54
55    fn parse_fraction(&self, ptr: &mut usize, value: &[u8]) -> Option<FloatValue> {
56        let mut fraction: Option<(Result<u64, BigUint>, Result<u64, BigUint>)> = None;
57
58        loop {
59            match value.get(*ptr).map(|b| *b) {
60                Some(val @ b'0'..=b'9') => {
61                    *ptr += 1;
62                    let val = val - b'0';
63
64                    let mut f = if fraction.is_some() {
65                        fraction.unwrap()
66                    } else {
67                        (Ok(0), Ok(1))
68                    };
69
70                    f.0 = match f.0 {
71                        Ok(u) => {
72                            if let Some(nval) = u.checked_mul(10) {
73                                if let Some(nval) = nval.checked_add(val as u64) {
74                                    Ok(nval)
75                                } else {
76                                    Err(BigUint::from(nval) + BigUint::from(val as u64))
77                                }
78                            } else {
79                                Err(BigUint::from(u) * BigUint::from(10u8)
80                                    + BigUint::from(val as u64))
81                            }
82                        }
83                        Err(b) => Err(b * BigUint::from(10u8) + BigUint::from(val as u64)),
84                    };
85
86                    f.1 = match f.1 {
87                        Ok(u) => {
88                            if let Some(nval) = u.checked_mul(10) {
89                                Ok(nval)
90                            } else {
91                                Err(BigUint::from(u) * BigUint::from(10u8))
92                            }
93                        }
94                        Err(b) => Err(b * BigUint::from(10u8)),
95                    };
96
97                    fraction = Some((f.0, f.1));
98                }
99                _ => break,
100            }
101        }
102
103        if let Some((num, den)) = fraction {
104            if num.is_ok() && den.is_ok() {
105                Some(FloatValue::from(Fraction::new(
106                    num.ok().unwrap(),
107                    den.ok().unwrap(),
108                )))
109            } else if num.is_ok() {
110                Some(FloatValue::from(BigFraction::new(
111                    BigUint::from(num.ok().unwrap()),
112                    den.err().unwrap(),
113                )))
114            } else if den.is_ok() {
115                Some(FloatValue::from(BigFraction::new(
116                    num.err().unwrap(),
117                    BigUint::from(den.ok().unwrap()),
118                )))
119            } else {
120                Some(FloatValue::from(BigFraction::new(
121                    num.err().unwrap(),
122                    den.err().unwrap(),
123                )))
124            }
125        } else {
126            None
127        }
128    }
129}
130
131impl Model for Timestamp {
132    fn get_tag(&self) -> Cow<'static, str> {
133        Self::get_tag()
134    }
135
136    fn as_any(&self) -> &dyn Any {
137        self
138    }
139
140    fn as_mut_any(&mut self) -> &mut dyn Any {
141        self
142    }
143
144    fn is_decodable(&self) -> bool {
145        true
146    }
147
148    fn is_encodable(&self) -> bool {
149        true
150    }
151
152    fn encode(
153        &self,
154        _renderer: &Renderer,
155        value: TaggedValue,
156        _tags: &mut dyn Iterator<Item = &(Cow<'static, str>, Cow<'static, str>)>,
157    ) -> Result<Rope, TaggedValue> {
158        let value: TimestampValue =
159            match <TaggedValue as Into<Result<TimestampValue, TaggedValue>>>::into(value) {
160                Ok(value) => value,
161                Err(value) => return Err(value),
162            };
163
164        let mut src = String::with_capacity(32);
165
166        if value.year.is_some() && value.month.is_some() && value.day.is_some() {
167            src.push_str(&format!(
168                "{:04}-{:02}-{:02}",
169                value.year.as_ref().unwrap(),
170                value.month.as_ref().unwrap(),
171                value.day.as_ref().unwrap()
172            ));
173        }
174
175        if value.hour.is_some() || value.minute.is_some() || value.second.is_some() {
176            if src.len() > 0 {
177                src.push_str("T")
178            };
179
180            src.push_str(&format!(
181                "{:02}:{:02}:{:02}",
182                if let Some(h) = value.hour.as_ref() {
183                    *h
184                } else {
185                    0
186                },
187                if let Some(m) = value.minute.as_ref() {
188                    *m
189                } else {
190                    0
191                },
192                if let Some(s) = value.second.as_ref() {
193                    *s
194                } else {
195                    0
196                }
197            ));
198
199            let fi = if let Some(f) = value.fraction.as_ref() {
200                let f = f.clone().format_as_float();
201                if let Some(f) = f {
202                    src.push_str(&f[1..]);
203                    true
204                } else {
205                    false
206                }
207            } else {
208                true
209            };
210
211            if !fi {
212                return Err(TaggedValue::from(value));
213            }
214
215            if let Some(h) = value.tz_hour.as_ref() {
216                if *h > 0 {
217                    src.push_str(&format!("{:+02}", h));
218                } else {
219                    src.push_str(&format!("{:+03}", h));
220                }
221
222                if let Some(ref m) = value.tz_minute.as_ref() {
223                    src.push_str(&format!(":{:02}", m));
224                }
225            }
226        }
227
228        Ok(Rope::from(Node::String(EncodedString::from(
229            src.into_bytes(),
230        ))))
231    }
232
233    fn decode(&self, explicit: bool, value: &[u8]) -> Result<TaggedValue, ()> {
234        let mut ptr: usize = 0;
235
236        let mut state: u8 = 0;
237
238        const STATE_YEAR: u8 = 1;
239        const STATE_MONTH: u8 = 2;
240        const STATE_DAY: u8 = 4;
241        const STATE_HOUR: u8 = 8;
242        const STATE_MINUTE: u8 = 16;
243        const STATE_SECOND: u8 = 32;
244        const STATE_TZ_HOUR: u8 = 64;
245        const STATE_TZ_MINUTE: u8 = 128;
246
247        let mut dt = TimestampValue::new();
248
249        let mut quote_state = 0; // 1 - single, 2 - double
250
251        'top: loop {
252            if ptr >= value.len() {
253                break;
254            }
255
256            if explicit && ptr == 0 && quote_state == 0 {
257                match value.get(ptr).map(|b| *b) {
258                    Some(b'\'') => {
259                        ptr += 1;
260                        quote_state = 1;
261                        continue 'top;
262                    }
263                    Some(b'"') => {
264                        ptr += 1;
265                        quote_state = 2;
266                        continue 'top;
267                    }
268                    _ => (),
269                }
270            }
271
272            if state == 0 {
273                let ltz = if let Some(b'-') = value.get(ptr).map(|b| *b) {
274                    ptr += 1;
275                    true
276                } else {
277                    false
278                };
279
280                let figure = self.parse_figure(&mut ptr, value);
281
282                if figure.is_none() {
283                    return Err(());
284                }
285
286                if !ltz && figure.unwrap() >= 0 && figure.unwrap() < 25 {
287                    if let Some(b':') = value.get(ptr).map(|b| *b) {
288                        state = STATE_HOUR;
289                        dt = dt.hour(figure.unwrap() as u8);
290                    }
291                }
292
293                if state == 0
294                    && figure.unwrap() >= (i32::MIN as i64)
295                    && figure.unwrap() <= (i32::MAX as i64)
296                {
297                    state = STATE_YEAR;
298                    dt = dt.year((figure.unwrap() * if ltz { -1 } else { 1 }) as i32);
299                }
300
301                continue;
302            } else if state == STATE_YEAR {
303                if let Some(b'-') = value.get(ptr).map(|b| *b) {
304                    ptr += 1;
305
306                    let figure = self.parse_figure(&mut ptr, value);
307
308                    if figure.is_none() {
309                        return Err(());
310                    }
311
312                    if figure.unwrap() > 0 && figure.unwrap() < 13 {
313                        state = state | STATE_MONTH;
314                        dt = dt.month(figure.unwrap() as u8);
315                    } else {
316                        return Err(());
317                    }
318                } else {
319                    return Err(());
320                }
321
322                continue;
323            } else if state == STATE_YEAR | STATE_MONTH {
324                if let Some(b'-') = value.get(ptr).map(|b| *b) {
325                    ptr += 1;
326
327                    let figure = self.parse_figure(&mut ptr, value);
328
329                    if figure.is_none() {
330                        return Err(());
331                    }
332
333                    if figure.unwrap() > 0 && figure.unwrap() < 32 {
334                        state = state | STATE_DAY;
335                        dt = dt.day(figure.unwrap() as u8);
336                    } else {
337                        return Err(());
338                    }
339                } else {
340                    return Err(());
341                }
342
343                continue;
344            } else if state == STATE_YEAR | STATE_MONTH | STATE_DAY {
345                match value.get(ptr).map(|b| *b) {
346                    Some(b'T') | Some(b' ') | Some(b't') | Some(b'\t') => {
347                        ptr += 1;
348                    }
349                    _ => return Err(()),
350                };
351
352                let figure = self.parse_figure(&mut ptr, value);
353
354                if figure.is_none() {
355                    return Err(());
356                }
357
358                if figure.unwrap() >= 0 && figure.unwrap() < 25 {
359                    state = state | STATE_HOUR;
360                    dt = dt.hour(figure.unwrap() as u8);
361                } else {
362                    return Err(());
363                }
364
365                continue;
366            } else if state & (STATE_HOUR | STATE_MINUTE | STATE_SECOND) == STATE_HOUR {
367                if let Some(b':') = value.get(ptr).map(|b| *b) {
368                    ptr += 1;
369
370                    let figure = self.parse_figure(&mut ptr, value);
371
372                    if figure.is_none() {
373                        return Err(());
374                    }
375
376                    if figure.unwrap() >= 0 && figure.unwrap() < 61 {
377                        state = state | STATE_MINUTE;
378                        dt = dt.minute(figure.unwrap() as u8);
379                    } else {
380                        return Err(());
381                    }
382                } else {
383                    return Err(());
384                }
385
386                continue;
387            } else if state & (STATE_HOUR | STATE_MINUTE | STATE_SECOND)
388                == (STATE_HOUR | STATE_MINUTE)
389            {
390                if let Some(b':') = value.get(ptr).map(|b| *b) {
391                    ptr += 1;
392
393                    let figure = self.parse_figure(&mut ptr, value);
394
395                    if figure.is_none() {
396                        return Err(());
397                    }
398
399                    if figure.unwrap() >= 0 && figure.unwrap() < 61 {
400                        state = state | STATE_SECOND;
401                        dt = dt.second(figure.unwrap() as u8);
402                    } else {
403                        return Err(());
404                    }
405                } else {
406                    return Err(());
407                }
408
409                continue;
410            } else if state & (STATE_HOUR | STATE_MINUTE | STATE_SECOND)
411                == (STATE_HOUR | STATE_MINUTE | STATE_SECOND)
412            {
413                if let Some(b'.') = value.get(ptr).map(|b| *b) {
414                    ptr += 1;
415
416                    let fraction = self.parse_fraction(&mut ptr, value);
417
418                    if fraction.is_none() {
419                        return Err(());
420                    }
421
422                    dt = dt.fraction(fraction.unwrap());
423                }
424
425                if ptr >= value.len() {
426                    break 'top;
427                }
428
429                loop {
430                    match value.get(ptr).map(|b| *b) {
431                        Some(b' ') | Some(b'\t') => {
432                            ptr += 1;
433                        }
434                        _ => break,
435                    };
436                }
437
438                match value.get(ptr).map(|b| *b) {
439                    Some(b'z') | Some(b'Z') => {
440                        ptr += 1;
441                        state = state | STATE_TZ_HOUR | STATE_TZ_MINUTE;
442                        dt = dt.tz_hour(0).tz_minute(0);
443                    }
444                    Some(b'-') => {
445                        ptr += 1;
446                        let figure = self.parse_figure(&mut ptr, value);
447                        if figure.is_none() {
448                            return Err(());
449                        }
450                        if figure.unwrap() >= 0 && figure.unwrap() < 25 {
451                            state = state | STATE_TZ_HOUR;
452                            dt = dt.tz_hour((figure.unwrap() as i8) * -1);
453                        } else {
454                            return Err(());
455                        }
456                    }
457                    Some(b'+') => {
458                        ptr += 1;
459                        let figure = self.parse_figure(&mut ptr, value);
460                        if figure.is_none() {
461                            return Err(());
462                        }
463                        if figure.unwrap() >= 0 && figure.unwrap() < 25 {
464                            state = state | STATE_TZ_HOUR;
465                            dt = dt.tz_hour(figure.unwrap() as i8);
466                        } else {
467                            return Err(());
468                        }
469                    }
470                    _ => (),
471                };
472
473                if state & STATE_TZ_HOUR == 0 && value.len() > ptr {
474                    let figure = self.parse_figure(&mut ptr, value);
475
476                    if figure.is_none() {
477                        return Err(());
478                    }
479
480                    if figure.unwrap() >= 0 && figure.unwrap() < 25 {
481                        state = state | STATE_TZ_HOUR;
482
483                        dt = dt.tz_hour(figure.unwrap() as i8);
484                    } else {
485                        return Err(());
486                    }
487
488                    continue;
489                }
490
491                if state & STATE_TZ_MINUTE == 0 && value.len() > ptr {
492                    match value.get(ptr).map(|b| *b) {
493                        Some(b':') => {
494                            ptr += 1;
495                        }
496                        _ => return Err(()),
497                    };
498
499                    let figure = self.parse_figure(&mut ptr, value);
500
501                    if figure.is_none() {
502                        return Err(());
503                    }
504
505                    if figure.unwrap() >= 0 && figure.unwrap() < 61 {
506                        state = state | STATE_TZ_MINUTE;
507
508                        dt = dt.tz_minute(figure.unwrap() as u8);
509                    } else {
510                        return Err(());
511                    }
512
513                    continue;
514                }
515            } else {
516                return Err(());
517            }
518
519            break;
520        }
521
522        if state > 0 {
523            if quote_state > 0 {
524                match value.get(ptr).map(|b| *b) {
525                    Some(b'\'') if quote_state == 1 => (),
526                    Some(b'"') if quote_state == 2 => (),
527                    _ => return Err(()),
528                };
529            }
530
531            Ok(TaggedValue::from(dt))
532        } else {
533            Err(())
534        }
535    }
536}
537
538#[derive(Clone, Debug)]
539pub struct TimestampValue {
540    pub year: Option<i32>,
541    pub month: Option<u8>,
542    pub day: Option<u8>,
543    pub hour: Option<u8>,
544    pub minute: Option<u8>,
545    pub second: Option<u8>,
546    pub fraction: Option<FloatValue>,
547    pub tz_hour: Option<i8>,
548    pub tz_minute: Option<u8>,
549}
550
551impl TimestampValue {
552    pub fn new() -> TimestampValue {
553        TimestampValue {
554            year: None,
555            month: None,
556            day: None,
557            hour: None,
558            minute: None,
559            second: None,
560            fraction: None,
561            tz_hour: None,
562            tz_minute: None,
563        }
564    }
565
566    pub fn year(mut self, val: i32) -> TimestampValue {
567        self.year = Some(val);
568        self
569    }
570
571    pub fn month(mut self, val: u8) -> TimestampValue {
572        self.month = Some(val);
573        self
574    }
575
576    pub fn day(mut self, val: u8) -> TimestampValue {
577        self.day = Some(val);
578        self
579    }
580
581    pub fn hour(mut self, val: u8) -> TimestampValue {
582        self.hour = Some(val);
583        self
584    }
585
586    pub fn minute(mut self, val: u8) -> TimestampValue {
587        self.minute = Some(val);
588        self
589    }
590
591    pub fn second(mut self, val: u8) -> TimestampValue {
592        self.second = Some(val);
593        self
594    }
595
596    pub fn fraction(mut self, val: FloatValue) -> TimestampValue {
597        self.fraction = Some(val);
598        self
599    }
600
601    pub fn tz_hour(mut self, val: i8) -> TimestampValue {
602        self.tz_hour = Some(val);
603        self
604    }
605
606    pub fn tz_minute(mut self, val: u8) -> TimestampValue {
607        self.tz_minute = Some(val);
608        self
609    }
610}
611
612impl Tagged for TimestampValue {
613    fn get_tag(&self) -> Cow<'static, str> {
614        Cow::from(TAG)
615    }
616
617    fn as_any(&self) -> &dyn Any {
618        self as &dyn Any
619    }
620
621    fn as_mut_any(&mut self) -> &mut dyn Any {
622        self as &mut dyn Any
623    }
624}
625
626#[cfg(all(test, not(feature = "dev")))]
627mod tests {
628    // TODO: tests on parsing failures
629
630    use super::*;
631    extern crate num;
632
633    use super::fraction::Fraction;
634
635    use crate::model::yaml::float::FloatValue;
636    use crate::model::{Renderer, Tagged};
637
638    use std::iter;
639
640    #[test]
641    fn tag() {
642        let ts_coder = Timestamp; // ::new (&get_charset_utf8 ());
643
644        assert_eq!(ts_coder.get_tag(), TAG);
645    }
646
647    macro_rules! encoded_dt_is {
648        ($coder:expr, $dt:expr, $str:expr) => {{
649            let renderer = Renderer; // ::new (&get_charset_utf8 ());
650            if let Ok(rope) = $coder.encode(&renderer, TaggedValue::from($dt), &mut iter::empty()) {
651                let encoded = rope.render(&renderer);
652                assert_eq!($str.to_string().into_bytes(), encoded);
653            } else {
654                assert!(false)
655            }
656        }};
657    }
658
659    #[test]
660    fn encode() {
661        let ts_coder = Timestamp; // ::new (&get_charset_utf8 ());
662
663        encoded_dt_is!(
664            ts_coder,
665            TimestampValue::new().year(2016).month(1).day(16),
666            "2016-01-16"
667        );
668        encoded_dt_is!(
669            ts_coder,
670            TimestampValue::new().hour(18).minute(58).second(3),
671            "18:58:03"
672        );
673
674        let dt = TimestampValue::new()
675            .year(2016)
676            .month(1)
677            .day(16)
678            .hour(18)
679            .minute(58)
680            .second(3);
681        encoded_dt_is!(ts_coder, dt.clone(), "2016-01-16T18:58:03");
682
683        let dt = dt
684            .fraction(FloatValue::from(Fraction::new(25u8, 100u8)))
685            .tz_hour(12);
686        encoded_dt_is!(ts_coder, dt.clone(), "2016-01-16T18:58:03.25+12");
687
688        let dt = dt.tz_hour(-2);
689        encoded_dt_is!(ts_coder, dt.clone(), "2016-01-16T18:58:03.25-02");
690
691        let dt = dt.tz_minute(25);
692        encoded_dt_is!(ts_coder, dt, "2016-01-16T18:58:03.25-02:25");
693    }
694
695    #[test]
696    fn decode() {
697        let ts_coder = Timestamp; // ::new (&get_charset_utf8 ());
698
699        if let Ok(tagged) = ts_coder.decode(true, "2016-01-16".as_bytes()) {
700            assert_eq!(tagged.get_tag(), Cow::from(TAG));
701
702            if let Some(decoded) = tagged.as_any().downcast_ref::<TimestampValue>() {
703                assert!(decoded.year.is_some());
704                assert_eq!(decoded.year.unwrap(), 2016);
705
706                assert!(decoded.month.is_some());
707                assert_eq!(decoded.month.unwrap(), 1);
708
709                assert!(decoded.day.is_some());
710                assert_eq!(decoded.day.unwrap(), 16);
711
712                assert!(decoded.hour.is_none());
713                assert!(decoded.minute.is_none());
714                assert!(decoded.second.is_none());
715                assert!(decoded.fraction.is_none());
716                assert!(decoded.tz_hour.is_none());
717                assert!(decoded.tz_minute.is_none());
718            } else {
719                assert!(false)
720            }
721        } else {
722            assert!(false)
723        }
724
725        if let Ok(tagged) = ts_coder.decode(true, "23:59:11".as_bytes()) {
726            assert_eq!(tagged.get_tag(), Cow::from(TAG));
727
728            if let Some(decoded) = tagged.as_any().downcast_ref::<TimestampValue>() {
729                assert!(decoded.year.is_none());
730                assert!(decoded.month.is_none());
731                assert!(decoded.day.is_none());
732
733                assert!(decoded.hour.is_some());
734                assert_eq!(decoded.hour.unwrap(), 23);
735
736                assert!(decoded.minute.is_some());
737                assert_eq!(decoded.minute.unwrap(), 59);
738
739                assert!(decoded.second.is_some());
740                assert_eq!(decoded.second.unwrap(), 11);
741
742                assert!(decoded.fraction.is_none());
743                assert!(decoded.tz_hour.is_none());
744                assert!(decoded.tz_minute.is_none());
745            } else {
746                assert!(false)
747            }
748        } else {
749            assert!(false)
750        }
751
752        if let Ok(tagged) = ts_coder.decode(true, "2016-06-03T23:59:11.0045-12:25".as_bytes()) {
753            assert_eq!(tagged.get_tag(), Cow::from(TAG));
754
755            if let Some(decoded) = tagged.as_any().downcast_ref::<TimestampValue>() {
756                assert!(decoded.year.is_some());
757                assert_eq!(decoded.year.unwrap(), 2016);
758
759                assert!(decoded.month.is_some());
760                assert_eq!(decoded.month.unwrap(), 6);
761
762                assert!(decoded.day.is_some());
763                assert_eq!(decoded.day.unwrap(), 3);
764
765                assert!(decoded.hour.is_some());
766                assert_eq!(decoded.hour.unwrap(), 23);
767
768                assert!(decoded.minute.is_some());
769                assert_eq!(decoded.minute.unwrap(), 59);
770
771                assert!(decoded.second.is_some());
772                assert_eq!(decoded.second.unwrap(), 11);
773
774                assert!(decoded.fraction.is_some());
775                let frac = decoded.fraction.as_ref().unwrap(); // 0.0045 == 9/2000
776                assert_eq!(
777                    format!("{:.4}", Fraction::new(9u8, 2000u16)),
778                    frac.format_as_float().unwrap()
779                );
780
781                assert!(decoded.tz_hour.is_some());
782                assert_eq!(decoded.tz_hour.unwrap(), -12);
783
784                assert!(decoded.tz_minute.is_some());
785                assert_eq!(decoded.tz_minute.unwrap(), 25);
786            } else {
787                assert!(false)
788            }
789        } else {
790            assert!(false)
791        }
792    }
793}