1use std::str::FromStr as _;
2
3use chrono::prelude::*;
4use core::cmp::Ordering;
5use limbo_ext::ValueType;
6use thiserror::Error;
7
8use limbo_ext::{register_extension, scalar, ResultCode, Value};
9
10mod time;
11
12use time::*;
13
14register_extension! {
15 scalars: {
16 time_now,
17 time_date,
18 make_date,
19 make_timestamp,
20 time_get,
21 time_get_year,
22 time_get_month,
23 time_get_day,
24 time_get_hour,
25 time_get_minute,
26 time_get_second,
27 time_get_nano,
28 time_get_weekday,
29 time_get_yearday,
30 time_get_isoyear,
31 time_get_isoweek,
32 time_unix,
33 to_timestamp,
34 time_milli,
35 time_micro,
36 time_nano,
37 time_to_unix,
38 time_to_milli,
39 time_to_micro,
40 time_to_nano,
41 time_after,
42 time_before,
43 time_compare,
44 time_equal,
45 dur_ns,
46 dur_us,
47 dur_ms,
48 dur_s,
49 dur_m,
50 dur_h,
51 time_add,
52 time_add_date,
53 time_sub,
54 time_since,
55 time_until,
56 time_trunc,
57 time_round,
58 time_fmt_iso,
59 time_fmt_datetime,
60 time_fmt_date,
61 time_fmt_time,
62 time_parse,
63 },
64}
65
66macro_rules! ok_tri {
67 ($e:expr) => {
68 match $e {
69 Some(val) => val,
70 None => return Value::error(ResultCode::Error),
71 }
72 };
73 ($e:expr, $msg:expr) => {
74 match $e {
75 Some(val) => val,
76 None => return Value::error_with_message($msg.to_string()),
77 }
78 };
79}
80
81macro_rules! tri {
82 ($e:expr) => {
83 match $e {
84 Ok(val) => val,
85 Err(err) => return Value::error_with_message(err.to_string()),
86 }
87 };
88 ($e:expr, $msg:expr) => {
89 match $e {
90 Ok(val) => val,
91 Err(_) => return Value::error_with_message($msg.to_string()),
92 }
93 };
94}
95
96macro_rules! value_tri {
98 ($e:expr, $val:pat) => {
99 match $e {
100 $val => (),
101 _ => return Value::error(ResultCode::InvalidArgs),
102 }
103 };
104 ($e:expr, $val:pat, $msg:expr) => {
105 match $e {
106 $val => (),
107 _ => return Value::error_with_message($msg.to_string()),
108 }
109 };
110}
111
112#[derive(Error, Debug)]
113pub enum TimeError {
114 #[error("invalid timezone offset")]
116 InvalidOffset,
117 #[error("invalid datetime format")]
118 InvalidFormat,
119 #[error("invalid time blob size")]
121 InvalidSize,
122 #[error("mismatch time blob version")]
124 MismatchVersion,
125 #[error("unknown field")]
126 UnknownField(#[from] <TimeField as ::core::str::FromStr>::Err),
127 #[error("rounding error")]
128 RoundingError(#[from] chrono::RoundingError),
129 #[error("time creation error")]
130 CreationError,
131}
132
133type Result<T> = core::result::Result<T, TimeError>;
134
135#[scalar(name = "time_now", alias = "now")]
136fn time_now(args: &[Value]) -> Value {
137 if !args.is_empty() {
138 return Value::error(ResultCode::InvalidArgs);
139 }
140 let t = Time::new();
141
142 t.into_blob()
143}
144
145fn time_date_internal(args: &[Value]) -> Value {
155 if args.len() != 3 && args.len() != 6 && args.len() != 7 && args.len() != 8 {
156 return Value::error(ResultCode::InvalidArgs);
157 }
158
159 for arg in args {
160 value_tri!(
161 arg.value_type(),
162 ValueType::Integer,
163 "all parameters should be integers"
164 );
165 }
166
167 let year = ok_tri!(args[0].to_integer());
168 let month = ok_tri!(args[1].to_integer());
169 let day = ok_tri!(args[2].to_integer());
170 let mut hour = 0;
171 let mut minutes = 0;
172 let mut seconds = 0;
173 let mut nano_secs = 0;
174
175 if args.len() >= 6 {
176 hour = ok_tri!(args[3].to_integer());
177 minutes = ok_tri!(args[4].to_integer());
178 seconds = ok_tri!(args[5].to_integer());
179 }
180
181 if args.len() >= 7 {
182 nano_secs = ok_tri!(args[6].to_integer());
183 }
184
185 if args.len() == 8 {
186 let offset_sec = ok_tri!(args[7].to_integer()) as i32;
187 seconds -= offset_sec as i64;
188 }
189
190 let t = Time::time_date(
191 year as i32,
192 month as i32,
193 day,
194 hour,
195 minutes,
196 seconds,
197 nano_secs,
198 FixedOffset::east_opt(0).unwrap(),
199 );
200
201 let t = tri!(t);
202
203 t.into_blob()
204}
205
206#[scalar(name = "time_date")]
207fn time_date(args: &[Value]) {
208 time_date_internal(args)
209}
210
211#[scalar(name = "make_date")]
212fn make_date(args: &[Value]) -> Value {
213 if args.len() != 3 {
214 return Value::error(ResultCode::InvalidArgs);
215 }
216
217 time_date_internal(args)
218}
219
220#[scalar(name = "make_timestamp")]
221fn make_timestamp(args: &[Value]) -> Value {
222 if args.len() != 6 {
223 return Value::error(ResultCode::InvalidArgs);
224 }
225
226 time_date_internal(args)
227}
228
229#[scalar(name = "time_get", alias = "date_part")]
230fn time_get(args: &[Value]) -> Value {
231 if args.len() != 2 {
232 return Value::error(ResultCode::InvalidArgs);
233 }
234
235 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
236
237 let t = tri!(Time::try_from(blob));
238
239 let field = ok_tri!(args[1].to_text(), "2nd parameter: should be a field name");
240
241 let field = tri!(TimeField::from_str(field));
242
243 t.time_get(field)
244}
245
246#[scalar(name = "time_get_year")]
247fn time_get_year(args: &[Value]) -> Value {
248 if args.len() != 1 {
249 return Value::error(ResultCode::InvalidArgs);
250 }
251
252 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
253
254 let t = tri!(Time::try_from(blob));
255
256 t.time_get(TimeField::Year)
257}
258
259#[scalar(name = "time_get_month")]
260fn time_get_month(args: &[Value]) -> Value {
261 if args.len() != 1 {
262 return Value::error(ResultCode::InvalidArgs);
263 }
264
265 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
266
267 let t = tri!(Time::try_from(blob));
268
269 t.time_get(TimeField::Month)
270}
271
272#[scalar(name = "time_get_day")]
273fn time_get_day(args: &[Value]) -> Value {
274 if args.len() != 1 {
275 return Value::error(ResultCode::InvalidArgs);
276 }
277
278 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
279
280 let t = tri!(Time::try_from(blob));
281
282 t.time_get(TimeField::Day)
283}
284
285#[scalar(name = "time_get_hour")]
286fn time_get_hour(args: &[Value]) -> Value {
287 if args.len() != 1 {
288 return Value::error(ResultCode::InvalidArgs);
289 }
290
291 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
292
293 let t = tri!(Time::try_from(blob));
294
295 t.time_get(TimeField::Hour)
296}
297
298#[scalar(name = "time_get_minute")]
299fn time_get_minute(args: &[Value]) -> Value {
300 if args.len() != 1 {
301 return Value::error(ResultCode::InvalidArgs);
302 }
303
304 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
305
306 let t = tri!(Time::try_from(blob));
307
308 t.time_get(TimeField::Minute)
309}
310
311#[scalar(name = "time_get_second")]
312fn time_get_second(args: &[Value]) -> Value {
313 if args.len() != 1 {
314 return Value::error(ResultCode::InvalidArgs);
315 }
316
317 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
318
319 let t = tri!(Time::try_from(blob));
320
321 Value::from_integer(t.get_second())
322}
323
324#[scalar(name = "time_get_nano")]
325fn time_get_nano(args: &[Value]) -> Value {
326 if args.len() != 1 {
327 return Value::error(ResultCode::InvalidArgs);
328 }
329
330 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
331
332 let t = tri!(Time::try_from(blob));
333
334 Value::from_integer(t.get_nanosecond())
335}
336
337#[scalar(name = "time_get_weekday")]
338fn time_get_weekday(args: &[Value]) -> Value {
339 if args.len() != 1 {
340 return Value::error(ResultCode::InvalidArgs);
341 }
342
343 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
344
345 let t = tri!(Time::try_from(blob));
346
347 t.time_get(TimeField::WeekDay)
348}
349
350#[scalar(name = "time_get_yearday")]
351fn time_get_yearday(args: &[Value]) -> Value {
352 if args.len() != 1 {
353 return Value::error(ResultCode::InvalidArgs);
354 }
355
356 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
357
358 let t = tri!(Time::try_from(blob));
359
360 t.time_get(TimeField::YearDay)
361}
362
363#[scalar(name = "time_get_isoyear")]
364fn time_get_isoyear(args: &[Value]) -> Value {
365 if args.len() != 1 {
366 return Value::error(ResultCode::InvalidArgs);
367 }
368
369 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
370
371 let t = tri!(Time::try_from(blob));
372
373 t.time_get(TimeField::IsoYear)
374}
375
376#[scalar(name = "time_get_isoweek")]
377fn time_get_isoweek(args: &[Value]) -> Value {
378 if args.len() != 1 {
379 return Value::error(ResultCode::InvalidArgs);
380 }
381
382 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
383
384 let t = tri!(Time::try_from(blob));
385
386 t.time_get(TimeField::IsoWeek)
387}
388
389fn time_unix_internal(args: &[Value]) -> Value {
390 if args.len() != 1 && args.len() != 2 {
391 return Value::error(ResultCode::InvalidArgs);
392 }
393
394 for arg in args {
395 value_tri!(
396 arg.value_type(),
397 ValueType::Integer,
398 "all parameters should be integers"
399 );
400 }
401
402 let seconds = ok_tri!(args[0].to_integer());
403
404 let mut nano_sec = 0;
405
406 if args.len() == 2 {
407 nano_sec = ok_tri!(args[1].to_integer());
408 }
409
410 let dt = ok_tri!(DateTime::from_timestamp(seconds, nano_sec as u32));
411
412 let t = Time::from_datetime(dt);
413
414 t.into_blob()
415}
416
417#[scalar(name = "time_unix")]
418fn time_unix(args: &[Value]) -> Value {
419 time_unix_internal(args)
420}
421
422#[scalar(name = "to_timestamp")]
423fn to_timestamp(args: &[Value]) -> Value {
424 if args.len() != 1 {
425 return Value::error(ResultCode::InvalidArgs);
426 }
427
428 time_unix_internal(args)
429}
430
431#[scalar(name = "time_milli")]
432fn time_milli(args: &[Value]) -> Value {
433 if args.len() != 1 {
434 return Value::error(ResultCode::InvalidArgs);
435 }
436
437 value_tri!(
438 &args[0].value_type(),
439 ValueType::Integer,
440 "parameter should be an integer"
441 );
442
443 let millis = ok_tri!(args[0].to_integer());
444
445 let dt = ok_tri!(DateTime::from_timestamp_millis(millis));
446
447 let t = Time::from_datetime(dt);
448
449 t.into_blob()
450}
451
452#[scalar(name = "time_micro")]
453fn time_micro(args: &[Value]) -> Value {
454 if args.len() != 1 {
455 return Value::error(ResultCode::InvalidArgs);
456 }
457
458 value_tri!(
459 &args[0].value_type(),
460 ValueType::Integer,
461 "parameter should be an integer"
462 );
463
464 let micros = ok_tri!(args[0].to_integer());
465
466 let dt = ok_tri!(DateTime::from_timestamp_micros(micros));
467
468 let t = Time::from_datetime(dt);
469
470 t.into_blob()
471}
472
473#[scalar(name = "time_nano")]
474fn time_nano(args: &[Value]) -> Value {
475 if args.len() != 1 {
476 return Value::error(ResultCode::InvalidArgs);
477 }
478
479 value_tri!(
480 &args[0].value_type(),
481 ValueType::Integer,
482 "parameter should be an integer"
483 );
484
485 let nanos = ok_tri!(args[0].to_integer());
486
487 let dt = DateTime::from_timestamp_nanos(nanos);
488
489 let t = Time::from_datetime(dt);
490
491 t.into_blob()
492}
493
494#[scalar(name = "time_to_unix")]
495fn time_to_unix(args: &[Value]) -> Value {
496 if args.len() != 1 {
497 return Value::error(ResultCode::InvalidArgs);
498 }
499
500 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
501
502 let t = tri!(Time::try_from(blob));
503
504 Value::from_integer(t.to_unix())
505}
506
507#[scalar(name = "time_to_milli")]
508fn time_to_milli(args: &[Value]) -> Value {
509 if args.len() != 1 {
510 return Value::error(ResultCode::InvalidArgs);
511 }
512
513 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
514
515 let t = tri!(Time::try_from(blob));
516
517 Value::from_integer(t.to_unix_milli())
518}
519
520#[scalar(name = "time_to_micro")]
521fn time_to_micro(args: &[Value]) -> Value {
522 if args.len() != 1 {
523 return Value::error(ResultCode::InvalidArgs);
524 }
525
526 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
527
528 let t = tri!(Time::try_from(blob));
529
530 Value::from_integer(t.to_unix_micro())
531}
532
533#[scalar(name = "time_to_nano")]
534fn time_to_nano(args: &[Value]) -> Value {
535 if args.len() != 1 {
536 return Value::error(ResultCode::InvalidArgs);
537 }
538
539 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
540
541 let t = tri!(Time::try_from(blob));
542
543 Value::from_integer(ok_tri!(t.to_unix_nano()))
544}
545
546#[scalar(name = "time_after")]
549fn time_after(args: &[Value]) -> Value {
550 if args.len() != 2 {
551 return Value::error(ResultCode::InvalidArgs);
552 }
553
554 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
555
556 let t = tri!(Time::try_from(blob));
557
558 let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob");
559
560 let u = tri!(Time::try_from(blob));
561
562 Value::from_integer((t > u).into())
563}
564
565#[scalar(name = "time_before")]
566fn time_before(args: &[Value]) -> Value {
567 if args.len() != 2 {
568 return Value::error(ResultCode::InvalidArgs);
569 }
570
571 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
572
573 let t = tri!(Time::try_from(blob));
574
575 let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob");
576
577 let u = tri!(Time::try_from(blob));
578
579 Value::from_integer((t < u).into())
580}
581
582#[scalar(name = "time_compare")]
583fn time_compare(args: &[Value]) -> Value {
584 if args.len() != 2 {
585 return Value::error(ResultCode::InvalidArgs);
586 }
587
588 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
589
590 let t = tri!(Time::try_from(blob));
591
592 let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob");
593
594 let u = tri!(Time::try_from(blob));
595
596 let cmp = match ok_tri!(t.partial_cmp(&u)) {
597 Ordering::Less => -1,
598 Ordering::Greater => 1,
599 Ordering::Equal => 0,
600 };
601
602 Value::from_integer(cmp)
603}
604
605#[scalar(name = "time_equal")]
606fn time_equal(args: &[Value]) -> Value {
607 if args.len() != 2 {
608 return Value::error(ResultCode::InvalidArgs);
609 }
610
611 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
612
613 let t = tri!(Time::try_from(blob));
614
615 let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob");
616
617 let u = tri!(Time::try_from(blob));
618
619 Value::from_integer(t.eq(&u).into())
620}
621
622#[scalar(name = "dur_ns")]
626fn dur_ns(args: &[Value]) -> Value {
627 if !args.is_empty() {
628 return Value::error(ResultCode::InvalidArgs);
629 }
630
631 Value::from_integer(chrono::Duration::nanoseconds(1).num_nanoseconds().unwrap())
632}
633
634#[scalar(name = "dur_us")]
636fn dur_us(args: &[Value]) -> Value {
637 if !args.is_empty() {
638 return Value::error(ResultCode::InvalidArgs);
639 }
640
641 Value::from_integer(chrono::Duration::microseconds(1).num_nanoseconds().unwrap())
642}
643
644#[scalar(name = "dur_ms")]
646fn dur_ms(args: &[Value]) -> Value {
647 if !args.is_empty() {
648 return Value::error(ResultCode::InvalidArgs);
649 }
650
651 Value::from_integer(chrono::Duration::milliseconds(1).num_nanoseconds().unwrap())
652}
653
654#[scalar(name = "dur_s")]
656fn dur_s(args: &[Value]) -> Value {
657 if !args.is_empty() {
658 return Value::error(ResultCode::InvalidArgs);
659 }
660
661 Value::from_integer(chrono::Duration::seconds(1).num_nanoseconds().unwrap())
662}
663
664#[scalar(name = "dur_m")]
666fn dur_m(args: &[Value]) -> Value {
667 if !args.is_empty() {
668 return Value::error(ResultCode::InvalidArgs);
669 }
670
671 Value::from_integer(chrono::Duration::minutes(1).num_nanoseconds().unwrap())
672}
673
674#[scalar(name = "dur_h")]
676fn dur_h(args: &[Value]) -> Value {
677 if !args.is_empty() {
678 return Value::error(ResultCode::InvalidArgs);
679 }
680
681 Value::from_integer(chrono::Duration::hours(1).num_nanoseconds().unwrap())
682}
683
684#[scalar(name = "time_add", alias = "date_add")]
688fn time_add(args: &[Value]) -> Value {
689 if args.len() != 2 {
690 return Value::error(ResultCode::InvalidArgs);
691 }
692
693 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
694 let t = tri!(Time::try_from(blob));
695
696 value_tri!(
697 args[1].value_type(),
698 ValueType::Integer,
699 "2nd parameter: should be an integer"
700 );
701
702 let d = ok_tri!(args[1].to_integer());
703
704 let d = Duration::from(d);
705
706 t.add_duration(d).into_blob()
707}
708
709#[scalar(name = "time_add_date")]
710fn time_add_date(args: &[Value]) -> Value {
711 if args.len() != 2 && args.len() != 3 && args.len() != 4 {
712 return Value::error(ResultCode::InvalidArgs);
713 }
714
715 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
716 let t = tri!(Time::try_from(blob));
717
718 value_tri!(
719 args[1].value_type(),
720 ValueType::Integer,
721 "2nd parameter: should be an integer"
722 );
723
724 let years = ok_tri!(args[1].to_integer());
725 let mut months = 0;
726 let mut days = 0;
727
728 if args.len() >= 3 {
729 value_tri!(
730 args[2].value_type(),
731 ValueType::Integer,
732 "3rd parameter: should be an integer"
733 );
734
735 months = ok_tri!(args[2].to_integer());
736 }
737
738 if args.len() == 4 {
739 value_tri!(
740 args[3].value_type(),
741 ValueType::Integer,
742 "4th parameter: should be an integer"
743 );
744
745 days = ok_tri!(args[3].to_integer());
746 }
747
748 let t: Time = tri!(t.time_add_date(years as i32, months as i32, days));
749
750 t.into_blob()
751}
752
753fn time_sub_internal(t: Time, u: Time) -> Value {
757 let cmp = ok_tri!(t.partial_cmp(&u));
758
759 let diff = t - u;
760
761 let nano_secs = match diff.num_nanoseconds() {
762 Some(nano) => nano,
763 None => match cmp {
764 Ordering::Equal => ok_tri!(diff.num_nanoseconds()),
765 Ordering::Less => i64::MIN,
766 Ordering::Greater => i64::MAX,
767 },
768 };
769
770 Value::from_integer(nano_secs)
771}
772
773#[scalar(name = "time_sub", alias = "age")]
774fn time_sub(args: &[Value]) -> Value {
775 if args.len() != 2 {
776 return Value::error(ResultCode::InvalidArgs);
777 }
778
779 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
780 let t = tri!(Time::try_from(blob));
781
782 let blob = ok_tri!(args[1].to_blob(), "2nd parameter: should be a time blob");
783 let u = tri!(Time::try_from(blob));
784
785 time_sub_internal(t, u)
786}
787
788#[scalar(name = "time_since")]
789fn time_since(args: &[Value]) -> Value {
790 if args.len() != 1 {
791 return Value::error(ResultCode::InvalidArgs);
792 }
793
794 let now = Time::new();
795
796 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
797 let t = tri!(Time::try_from(blob));
798
799 time_sub_internal(now, t)
800}
801
802#[scalar(name = "time_until")]
803fn time_until(args: &[Value]) -> Value {
804 if args.len() != 1 {
805 return Value::error(ResultCode::InvalidArgs);
806 }
807
808 let now = Time::new();
809
810 let blob = ok_tri!(args[0].to_blob(), "parameter should be a time blob");
811 let t = tri!(Time::try_from(blob));
812
813 time_sub_internal(t, now)
814}
815
816#[scalar(name = "time_trunc", alias = "date_trunc")]
819fn time_trunc(args: &[Value]) -> Value {
820 if args.len() != 2 {
821 return Value::error(ResultCode::InvalidArgs);
822 }
823
824 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
825
826 let t = tri!(Time::try_from(blob));
827
828 match args[1].value_type() {
829 ValueType::Text => {
830 let field = ok_tri!(args[1].to_text());
831
832 let field = tri!(TimeRoundField::from_str(field));
833
834 tri!(t.trunc_field(field)).into_blob()
835 }
836 ValueType::Integer => {
837 let duration = ok_tri!(args[1].to_integer());
838 let duration = Duration::from(duration);
839
840 tri!(t.trunc_duration(duration)).into_blob()
841 }
842 _ => Value::error_with_message("2nd parameter: should be a field name".to_string()),
843 }
844}
845
846#[scalar(name = "time_round")]
847fn time_round(args: &[Value]) -> Value {
848 if args.len() != 2 {
849 return Value::error(ResultCode::InvalidArgs);
850 }
851
852 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
853
854 let t = tri!(Time::try_from(blob));
855
856 value_tri!(
857 args[1].value_type(),
858 ValueType::Integer,
859 "2nd parameter: should be an integer"
860 );
861
862 let duration = ok_tri!(args[1].to_integer());
863 let duration = Duration::from(duration);
864
865 tri!(t.round_duration(duration)).into_blob()
866}
867
868#[scalar(name = "time_fmt_iso")]
871fn time_fmt_iso(args: &[Value]) -> Value {
872 if args.len() != 1 && args.len() != 2 {
873 return Value::error(ResultCode::InvalidArgs);
874 }
875 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
876
877 let t = tri!(Time::try_from(blob));
878
879 let offset_sec = {
880 if args.len() == 2 {
881 value_tri!(
882 args[1].value_type(),
883 ValueType::Integer,
884 "2nd parameter: should be an integer"
885 );
886 ok_tri!(args[1].to_integer()) as i32
887 } else {
888 0
889 }
890 };
891
892 let fmt_str = tri!(t.fmt_iso(offset_sec));
893
894 Value::from_text(fmt_str)
895}
896
897#[scalar(name = "time_fmt_datetime")]
898fn time_fmt_datetime(args: &[Value]) -> Value {
899 if args.len() != 1 && args.len() != 2 {
900 return Value::error(ResultCode::InvalidArgs);
901 }
902 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
903
904 let t = tri!(Time::try_from(blob));
905
906 let offset_sec = {
907 if args.len() == 2 {
908 value_tri!(
909 args[1].value_type(),
910 ValueType::Integer,
911 "2nd parameter: should be an integer"
912 );
913 ok_tri!(args[1].to_integer()) as i32
914 } else {
915 0
916 }
917 };
918
919 let fmt_str = tri!(t.fmt_datetime(offset_sec));
920
921 Value::from_text(fmt_str)
922}
923
924#[scalar(name = "time_fmt_date")]
925fn time_fmt_date(args: &[Value]) -> Value {
926 if args.len() != 1 && args.len() != 2 {
927 return Value::error(ResultCode::InvalidArgs);
928 }
929 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
930
931 let t = tri!(Time::try_from(blob));
932
933 let offset_sec = {
934 if args.len() == 2 {
935 value_tri!(
936 args[1].value_type(),
937 ValueType::Integer,
938 "2nd parameter: should be an integer"
939 );
940 ok_tri!(args[1].to_integer()) as i32
941 } else {
942 0
943 }
944 };
945
946 let fmt_str = tri!(t.fmt_date(offset_sec));
947
948 Value::from_text(fmt_str)
949}
950
951#[scalar(name = "time_fmt_time")]
952fn time_fmt_time(args: &[Value]) -> Value {
953 if args.len() != 1 && args.len() != 2 {
954 return Value::error(ResultCode::InvalidArgs);
955 }
956 let blob = ok_tri!(args[0].to_blob(), "1st parameter: should be a time blob");
957
958 let t = tri!(Time::try_from(blob));
959
960 let offset_sec = {
961 if args.len() == 2 {
962 value_tri!(
963 args[1].value_type(),
964 ValueType::Integer,
965 "2nd parameter: should be an integer"
966 );
967 ok_tri!(args[1].to_integer()) as i32
968 } else {
969 0
970 }
971 };
972
973 let fmt_str = tri!(t.fmt_time(offset_sec));
974
975 Value::from_text(fmt_str)
976}
977
978#[scalar(name = "time_parse")]
979fn time_parse(args: &[Value]) -> Value {
980 if args.len() != 1 {
981 return Value::error(ResultCode::InvalidArgs);
982 }
983
984 let dt_str = ok_tri!(args[0].to_text());
985
986 if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(dt_str) {
987 return Time::from_datetime(dt.to_utc()).into_blob();
988 }
989
990 if let Ok(mut dt) = chrono::NaiveDateTime::parse_from_str(dt_str, "%Y-%m-%d %H:%M:%S") {
991 dt = dt.with_nanosecond(0).unwrap();
993 return Time::from_datetime(dt.and_utc()).into_blob();
994 }
995
996 if let Ok(date) = chrono::NaiveDate::parse_from_str(dt_str, "%Y-%m-%d") {
997 let dt = date
1000 .and_hms_opt(0, 0, 0)
1001 .unwrap()
1002 .with_nanosecond(0)
1003 .unwrap();
1004 return Time::from_datetime(dt.and_utc()).into_blob();
1005 }
1006
1007 let time = tri!(
1008 chrono::NaiveTime::parse_from_str(dt_str, "%H:%M:%S"),
1009 "error parsing datetime string"
1010 );
1011 let dt = NaiveDateTime::new(NaiveDate::from_ymd_opt(1, 1, 1).unwrap(), time)
1012 .with_nanosecond(0)
1013 .unwrap();
1014
1015 Time::from_datetime(dt.and_utc()).into_blob()
1016}