Skip to main content

triblespace_core/value/schemas/
time.rs

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