limbo_time/
lib.rs

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
96/// Checks to see if e's enum is of type val
97macro_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    /// Timezone offset is invalid
115    #[error("invalid timezone offset")]
116    InvalidOffset,
117    #[error("invalid datetime format")]
118    InvalidFormat,
119    /// Blob is not size of `TIME_BLOB_SIZE`
120    #[error("invalid time blob size")]
121    InvalidSize,
122    /// Blob time version not matching
123    #[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
145/// ```text
146/// time_date(year, month, day[, hour, min, sec[, nsec[, offset_sec]]])
147/// ```
148///
149/// Returns the Time corresponding to a given date/time. The time part (hour+minute+second), the nanosecond part, and the timezone offset part are all optional.
150///
151/// The `month`, `day`, `hour`, `min`, `sec`, and `nsec` values may be outside their usual ranges and will be normalized during the conversion. For example, October 32 converts to November 1.
152///
153/// If `offset_sec` is not 0, the source time is treated as being in a given timezone (with an offset in seconds east of UTC) and converted back to UTC.
154fn 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// Comparisons
547
548#[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// Duration Constants
623
624/// 1 nanosecond
625#[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/// 1 microsecond
635#[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/// 1 millisecond
645#[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/// 1 second
655#[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/// 1 minute
665#[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/// 1 hour
675#[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// Time Arithmetic
685
686/// Do not use `time_add` to add days, months or years. Use `time_add_date` instead.
687#[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
753/// Returns the duration between two time values t and u (in nanoseconds).
754/// If the result exceeds the maximum (or minimum) value that can be stored in a Duration,
755/// the maximum (or minimum) duration will be returned.
756fn 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// Rounding
817
818#[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// Formatting
869
870#[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        // Unwrap is safe here
992        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        // Unwrap is safe here
998
999        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}