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