Skip to main content

triblespace_core/value/schemas/
time.rs

1use crate::id::ExclusiveId;
2use crate::id::Id;
3use crate::id_hex;
4use crate::macros::entity;
5use crate::metadata;
6use crate::metadata::ConstMetadata;
7use crate::repo::BlobStore;
8use crate::trible::TribleSet;
9use crate::value::schemas::hash::Blake3;
10use crate::value::FromValue;
11use crate::value::ToValue;
12use crate::value::Value;
13use crate::value::ValueSchema;
14use std::convert::Infallible;
15
16use std::convert::TryInto;
17
18#[cfg(feature = "wasm")]
19use crate::blob::schemas::wasmcode::WasmCode;
20use hifitime::prelude::*;
21
22/// A value schema for a TAI interval.
23/// A TAI interval is a pair of TAI epochs.
24/// The interval is stored as two 128-bit signed integers, the lower and upper bounds.
25/// The lower bound is stored in the first 16 bytes and the upper bound is stored in the last 16 bytes.
26/// Both the lower and upper bounds are stored in little-endian byte order.
27/// Both the lower and upper bounds are inclusive. That is, the interval contains all TAI epochs between the lower and upper bounds.
28pub struct NsTAIInterval;
29
30impl ConstMetadata for NsTAIInterval {
31    fn id() -> Id {
32        id_hex!("675A2E885B12FCBC0EEC01E6AEDD8AA8")
33    }
34
35    fn describe<B>(blobs: &mut B) -> Result<TribleSet, B::PutError>
36    where
37        B: BlobStore<Blake3>,
38    {
39        let id = Self::id();
40        let description = blobs.put(
41            "Inclusive TAI interval encoded as two little-endian i128 nanosecond bounds. TAI is monotonic and does not include leap seconds, making it ideal for precise ordering.\n\nUse for time windows, scheduling, or event ranges where monotonic time matters. If you need civil time, time zones, or calendar semantics, store a separate representation alongside this interval.\n\nIntervals are inclusive on both ends. If you need half-open intervals or offsets, consider RangeU128 with your own epoch mapping.",
42        )?;
43        let tribles = entity! {
44            ExclusiveId::force_ref(&id) @
45                metadata::name: blobs.put("nstai_interval".to_string())?,
46                metadata::description: description,
47                metadata::tag: metadata::KIND_VALUE_SCHEMA,
48        };
49
50        #[cfg(feature = "wasm")]
51        let tribles = {
52            let mut tribles = tribles;
53            tribles += entity! { ExclusiveId::force_ref(&id) @
54                metadata::value_formatter: blobs.put(wasm_formatter::NSTAI_INTERVAL_WASM)?,
55            };
56            tribles
57        };
58        Ok(tribles)
59    }
60}
61
62#[cfg(feature = "wasm")]
63mod wasm_formatter {
64    use core::fmt::Write;
65
66    use triblespace_core_macros::value_formatter;
67
68    #[value_formatter]
69    pub(crate) fn nstai_interval(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
70        let mut buf = [0u8; 16];
71        buf.copy_from_slice(&raw[0..16]);
72        let lower = i128::from_le_bytes(buf);
73        buf.copy_from_slice(&raw[16..32]);
74        let upper = i128::from_le_bytes(buf);
75
76        write!(out, "{lower}..={upper}").map_err(|_| 1u32)?;
77        Ok(())
78    }
79}
80
81impl ValueSchema for NsTAIInterval {
82    type ValidationError = Infallible;
83}
84
85impl ToValue<NsTAIInterval> for (Epoch, Epoch) {
86    fn to_value(self) -> Value<NsTAIInterval> {
87        let lower = self.0.to_tai_duration().total_nanoseconds();
88        let upper = self.1.to_tai_duration().total_nanoseconds();
89
90        let mut value = [0; 32];
91        value[0..16].copy_from_slice(&lower.to_le_bytes());
92        value[16..32].copy_from_slice(&upper.to_le_bytes());
93        Value::new(value)
94    }
95}
96
97impl FromValue<'_, NsTAIInterval> for (Epoch, Epoch) {
98    fn from_value(v: &Value<NsTAIInterval>) -> Self {
99        let lower = i128::from_le_bytes(v.raw[0..16].try_into().unwrap());
100        let upper = i128::from_le_bytes(v.raw[16..32].try_into().unwrap());
101        let lower = Epoch::from_tai_duration(Duration::from_total_nanoseconds(lower));
102        let upper = Epoch::from_tai_duration(Duration::from_total_nanoseconds(upper));
103
104        (lower, upper)
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn hifitime_conversion() {
114        let epoch = Epoch::from_tai_duration(Duration::from_total_nanoseconds(0));
115        let time_in: (Epoch, Epoch) = (epoch, epoch);
116        let interval: Value<NsTAIInterval> = time_in.to_value();
117        let time_out: (Epoch, Epoch) = interval.from_value();
118
119        assert_eq!(time_in, time_out);
120    }
121}