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; '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 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; 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; 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; 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; 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(); 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}