triblespace_core/value/schemas/
time.rs1use 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
22pub 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}