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::{ConstDescribe, ConstId};
7use crate::repo::BlobStore;
8use crate::trible::Fragment;
9use crate::value::schemas::hash::Blake3;
10use crate::value::TryFromValue;
11use crate::value::ToValue;
12use crate::value::Value;
13use crate::value::ValueSchema;
14use std::convert::Infallible;
15
16use std::convert::TryInto;
17
18use hifitime::prelude::*;
19
20pub struct NsTAIInterval;
27
28impl ConstId for NsTAIInterval {
29 const ID: Id = id_hex!("675A2E885B12FCBC0EEC01E6AEDD8AA8");
30}
31
32impl ConstDescribe for NsTAIInterval {
33 fn describe<B>(blobs: &mut B) -> Result<Fragment, B::PutError>
34 where
35 B: BlobStore<Blake3>,
36 {
37 let id = Self::ID;
38 let description = blobs.put(
39 "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.",
40 )?;
41 let tribles = entity! {
42 ExclusiveId::force_ref(&id) @
43 metadata::name: blobs.put("nstai_interval")?,
44 metadata::description: description,
45 metadata::tag: metadata::KIND_VALUE_SCHEMA,
46 };
47
48 #[cfg(feature = "wasm")]
49 let tribles = {
50 let mut tribles = tribles;
51 tribles += entity! { ExclusiveId::force_ref(&id) @
52 metadata::value_formatter: blobs.put(wasm_formatter::NSTAI_INTERVAL_WASM)?,
53 };
54 tribles
55 };
56 Ok(tribles)
57 }
58}
59
60#[cfg(feature = "wasm")]
61mod wasm_formatter {
62 use core::fmt::Write;
63
64 use triblespace_core_macros::value_formatter;
65
66 #[value_formatter]
67 pub(crate) fn nstai_interval(raw: &[u8; 32], out: &mut impl Write) -> Result<(), u32> {
68 let mut buf = [0u8; 16];
69 buf.copy_from_slice(&raw[0..16]);
70 let lower = i128::from_le_bytes(buf);
71 buf.copy_from_slice(&raw[16..32]);
72 let upper = i128::from_le_bytes(buf);
73
74 write!(out, "{lower}..={upper}").map_err(|_| 1u32)?;
75 Ok(())
76 }
77}
78
79impl ValueSchema for NsTAIInterval {
80 type ValidationError = Infallible;
81}
82
83impl ToValue<NsTAIInterval> for (Epoch, Epoch) {
84 fn to_value(self) -> Value<NsTAIInterval> {
85 let lower = self.0.to_tai_duration().total_nanoseconds();
86 let upper = self.1.to_tai_duration().total_nanoseconds();
87
88 let mut value = [0; 32];
89 value[0..16].copy_from_slice(&lower.to_le_bytes());
90 value[16..32].copy_from_slice(&upper.to_le_bytes());
91 Value::new(value)
92 }
93}
94
95impl TryFromValue<'_, NsTAIInterval> for (Epoch, Epoch) {
96 type Error = Infallible;
97 fn try_from_value(v: &Value<NsTAIInterval>) -> Result<Self, Infallible> {
98 let (lower, upper): (i128, i128) = v.from_value();
99 let lower = Epoch::from_tai_duration(Duration::from_total_nanoseconds(lower));
100 let upper = Epoch::from_tai_duration(Duration::from_total_nanoseconds(upper));
101 Ok((lower, upper))
102 }
103}
104
105impl TryFromValue<'_, NsTAIInterval> for (i128, i128) {
106 type Error = Infallible;
107 fn try_from_value(v: &Value<NsTAIInterval>) -> Result<Self, Infallible> {
108 let lower = i128::from_le_bytes(v.raw[0..16].try_into().unwrap());
109 let upper = i128::from_le_bytes(v.raw[16..32].try_into().unwrap());
110 Ok((lower, upper))
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn hifitime_conversion() {
120 let epoch = Epoch::from_tai_duration(Duration::from_total_nanoseconds(0));
121 let time_in: (Epoch, Epoch) = (epoch, epoch);
122 let interval: Value<NsTAIInterval> = time_in.to_value();
123 let time_out: (Epoch, Epoch) = interval.from_value();
124
125 assert_eq!(time_in, time_out);
126 }
127
128 #[test]
129 fn nanosecond_conversion() {
130 let lower_ns: i128 = 1_000_000_000;
131 let upper_ns: i128 = 2_000_000_000;
132 let lower = Epoch::from_tai_duration(Duration::from_total_nanoseconds(lower_ns));
133 let upper = Epoch::from_tai_duration(Duration::from_total_nanoseconds(upper_ns));
134 let interval: Value<NsTAIInterval> = (lower, upper).to_value();
135
136 let (out_lower, out_upper): (i128, i128) = interval.from_value();
137 assert_eq!(out_lower, lower_ns);
138 assert_eq!(out_upper, upper_ns);
139 }
140}