vsf 0.3.4

Versatile Storage Format
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
use chrono::{DateTime, Duration, TimeZone, Utc};

/// Oscillations per Eagle second.
///
/// One Eagle second is defined as exactly 1,420,407,826 complete oscillation periods of the electromagnetic radiation emitted during the hydrogen-1 (protium) hyperfine transition between F=0 and F=1 ground states, as measured at the barycentric reference frame of the Milky Way-Andromeda galaxy system.
///
/// This is the 21cm hydrogen line at 1420.405751 MHz.
/// One oscillation period ≈ 704.032 picoseconds.
const OSCILLATIONS_PER_SECOND: u64 = 1_420_407_826;

/// Eagle Time type - represents time values as either oscillation counts or seconds.
///
/// Binary format: `[e][type][value...]`
/// - `[e]` = Eagle Time marker
/// - `[type]` = u/i/f (unsigned/signed/float)
/// - `[value]` = encoded value
///
/// **Integer types store actual oscillation counts:**
/// - Each count represents one complete 21cm hydrogen-1 hyperfine transition
/// - Precision: 704.032 picoseconds per oscillation
/// - u64: 411 years range at picosecond precision
/// - i64: ±206 years range at picosecond precision
///
/// **IEEE 754 Float types store seconds:**
/// - f32: ~2 minute effective precision (for timestamps since 1969)
/// - f64: ~200 nanosecond effective precision (for timestamps since 1969)
#[derive(Debug, Clone, PartialEq)]
#[allow(non_camel_case_types)]
pub enum EtType {
    u(u64), // Oscillation count
    i(i64), // Oscillation count
    f5(f32),  // Seconds as 32-bit float (2-3min precision)
    f6(f64),  // Seconds as 64-bit float (lossy)
}

/// EagleTime represents a point in time in the Eagle Time standard.
///
/// Stores time as either:
/// - **Oscillation counts** (integer types): Count of hydrogen-1 transition events since epoch (lunar landing)
/// - **Seconds** (float types): Floating-point seconds since epoch (lunar landing)
///
/// Eagle epoch: Apollo 11 lunar landing - July 20, 1969 at 20:17:40 UTC
/// (The moment "The Eagle has landed" was transmitted)
///
/// This definition:
/// - Uses the most abundant element in the universe (hydrogen-1)
/// - Is measurable with any 21cm radio receiver
/// - Accounts for gravitational time dilation in the frequency measurement
/// - Provides universal verifiability without trusted authorities
/// - Achieves picosecond precision with standard integer types
///
/// # Precision Characteristics
///
/// All integer types (u3-u7, i3-i7) are supported for oscillation counts.
/// Common configurations:
///
/// | Type | Range | Precision | Use Case |
/// |------|-------|-----------|----------|
/// | u32 | 1.5 years | 704 ps | Short-term timing |
/// | u64 | 411 years (1969-2380) | 704 ps | Absolute timestamps (default) |
/// | i64 | ±206 years | 704 ps | Time deltas |
/// | f32 | Arbitrary | ~2 min | Low-precision timestamps (legacy) |
/// | f64 | Arbitrary | ~200 ns | High-precision timestamps (legacy) |
///
/// Note: All u/i types use generic encoding, so u8/u16/u128/etc. are also valid.
#[derive(Debug, Clone)]
pub struct EagleTime {
    et_seconds: EtType,
}

impl EagleTime {
    /// Creates a new EagleTime instance from a VsfType.
    ///
    /// Integer types are interpreted as oscillation counts.
    /// Float types are interpreted as seconds.
    ///
    /// # Panics
    /// Panics if the VsfType is not a valid numeric variant or EagleTime type.
    pub fn new_from_vsf(value: crate::types::VsfType) -> Self {
        use crate::types::VsfType;

        let et_seconds = match value {
            // Handle VsfType::e (already an EagleTime wrapper)
            VsfType::e(et) => et,
            // Float types: already in seconds
            VsfType::f5(v) => EtType::f5(v),
            VsfType::f6(v) => EtType::f6(v),
            // Integer types: oscillation counts
            VsfType::u(v, false) => EtType::u(v as u64),
            VsfType::u3(v) => EtType::u(v as u64),
            VsfType::u4(v) => EtType::u(v as u64),
            VsfType::u5(v) => EtType::u(v as u64),
            VsfType::u6(v) => EtType::u(v as u64),
            VsfType::i(v) => EtType::i(v as i64),
            VsfType::i3(v) => EtType::i(v as i64),
            VsfType::i4(v) => EtType::i(v as i64),
            VsfType::i5(v) => EtType::i(v as i64),
            VsfType::i6(v) => EtType::i(v as i64),
            _ => panic!("EagleTime must be created with a valid numeric VsfType variant"),
        };
        EagleTime { et_seconds }
    }

    /// Creates a new EagleTime directly from an EtType
    pub fn new(et_seconds: EtType) -> Self {
        EagleTime { et_seconds }
    }

    /// Creates an EagleTime from a raw oscillation count
    pub fn from_oscillations(count: u64) -> Self {
        EagleTime {
            et_seconds: EtType::u(count),
        }
    }

    /// Creates an EagleTime from a signed oscillation count (for deltas)
    pub fn from_oscillations_signed(count: i64) -> Self {
        EagleTime {
            et_seconds: EtType::i(count),
        }
    }

    /// Creates an EagleTime from seconds (f64), converting to oscillation count
    pub fn from_seconds_f64(seconds: f64) -> Self {
        let oscillations = (seconds * OSCILLATIONS_PER_SECOND as f64).round() as u64;
        EagleTime {
            et_seconds: EtType::u(oscillations),
        }
    }

    /// Creates an EagleTime from seconds (f32), converting to oscillation count
    pub fn from_seconds_f32(seconds: f32) -> Self {
        let oscillations = (seconds * OSCILLATIONS_PER_SECOND as f32).round() as u64;
        EagleTime {
            et_seconds: EtType::u(oscillations),
        }
    }

    /// Converts the current EagleTime to a VsfType.
    pub fn to_vsf_type(&self) -> crate::types::VsfType {
        use crate::types::VsfType;

        match self.et_seconds {
            EtType::f5(v) => VsfType::f5(v),
            EtType::f6(v) => VsfType::f6(v),
            EtType::u(v) => VsfType::u6(v),
            EtType::i(v) => VsfType::i6(v),
        }
    }

    /// Converts the EagleTime to a UTC DateTime.
    ///
    /// For integer types, divides oscillation count by OSCILLATIONS_PER_SECOND.
    /// For float types, uses the stored seconds directly.
    ///
    /// Returns None if the timestamp is outside chrono's representable range.
    pub fn to_datetime_opt(&self) -> Option<DateTime<Utc>> {
        let eagle_epoch = Utc.with_ymd_and_hms(1969, 7, 20, 20, 17, 40).unwrap();
        let seconds = self.to_seconds_f64();
        let duration = Duration::from_std(std::time::Duration::from_secs_f64(seconds)).ok()?;
        Some(eagle_epoch + duration)
    }

    /// Converts the EagleTime to a UTC DateTime.
    ///
    /// For integer types, divides oscillation count by OSCILLATIONS_PER_SECOND.
    /// For float types, uses the stored seconds directly.
    ///
    /// Panics if the timestamp is outside chrono's representable range.
    /// For non-panicking version, use `to_datetime_opt()`.
    pub fn to_datetime(&self) -> DateTime<Utc> {
        self.to_datetime_opt().unwrap_or_else(|| {
            panic!(
                "Timestamp outside representable range: {:?}",
                self.et_seconds
            )
        })
    }

    /// Get a reference to the underlying EtType
    pub fn et_type(&self) -> &EtType {
        &self.et_seconds
    }

    /// Converts to f64 seconds, regardless of storage type.
    ///
    /// For integer types: divides oscillation count by OSCILLATIONS_PER_SECOND
    /// For float types: returns the stored seconds directly
    pub fn to_seconds_f64(&self) -> f64 {
        match self.et_seconds {
            EtType::u(oscillations) => oscillations as f64 / OSCILLATIONS_PER_SECOND as f64,
            EtType::i(oscillations) => oscillations as f64 / OSCILLATIONS_PER_SECOND as f64,
            EtType::f5(seconds) => seconds as f64,
            EtType::f6(seconds) => seconds,
        }
    }

    /// Converts to f32 seconds, regardless of storage type.
    pub fn to_seconds_f32(&self) -> f32 {
        match self.et_seconds {
            EtType::u(oscillations) => oscillations as f32 / OSCILLATIONS_PER_SECOND as f32,
            EtType::i(oscillations) => oscillations as f32 / OSCILLATIONS_PER_SECOND as f32,
            EtType::f5(seconds) => seconds,
            EtType::f6(seconds) => seconds as f32,
        }
    }

    /// Returns the oscillation count as i64 if stored as integer, None if stored as float.
    /// For unsigned values that may exceed i64::MAX, use `oscillations_u64()`.
    pub fn oscillations(&self) -> Option<i64> {
        match self.et_seconds {
            EtType::u(v) => Some(v as i64),
            EtType::i(v) => Some(v),
            EtType::f5(_) | EtType::f6(_) => None,
        }
    }

    /// Returns the oscillation count as u64 if stored as unsigned integer, None otherwise.
    pub fn oscillations_u64(&self) -> Option<u64> {
        match self.et_seconds {
            EtType::u(v) => Some(v),
            _ => None,
        }
    }

    /// Returns the picosecond precision timestamp (oscillations * 704.032).
    /// Returns None for float types since they've lost the picosecond precision.
    pub fn picoseconds(&self) -> Option<i128> {
        self.oscillations()
            .map(|osc| (osc as i128 * 704_032) / 1000)
    }
}

impl PartialEq for EagleTime {
    fn eq(&self, other: &Self) -> bool {
        // Compare at oscillation precision if both are integers
        match (&self.et_seconds, &other.et_seconds) {
            (EtType::u(a), EtType::u(b)) => a == b,
            (EtType::i(a), EtType::i(b)) => a == b,
            (EtType::u(a), EtType::i(b)) | (EtType::i(b), EtType::u(a)) => *a as i64 == *b,
            // Fall back to f64 comparison for any float involvement
            _ => self.to_seconds_f64() == other.to_seconds_f64(),
        }
    }
}

impl Eq for EagleTime {}

impl PartialOrd for EagleTime {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for EagleTime {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        // Compare at oscillation precision if both are integers
        match (&self.et_seconds, &other.et_seconds) {
            (EtType::u(a), EtType::u(b)) => a.cmp(b),
            (EtType::i(a), EtType::i(b)) => a.cmp(b),
            (EtType::u(a), EtType::i(b)) => (*a as i64).cmp(b),
            (EtType::i(a), EtType::u(b)) => a.cmp(&(*b as i64)),
            // Fall back to f64 comparison for any float involvement
            _ => self
                .to_seconds_f64()
                .partial_cmp(&other.to_seconds_f64())
                .unwrap_or(std::cmp::Ordering::Equal),
        }
    }
}

/// Converts a UTC DateTime to Eagle Time (as oscillation count in u64)
///
/// Returns the number of hydrogen-1 hyperfine oscillations since the Apollo 11 landing.
pub fn datetime_to_eagle_time(dt: DateTime<Utc>) -> EagleTime {
    let eagle_epoch = Utc.with_ymd_and_hms(1969, 7, 20, 20, 17, 40).unwrap();
    let duration = dt - eagle_epoch;

    // Calculate total seconds including subseconds
    let total_seconds =
        duration.num_seconds() as f64 + duration.subsec_nanos() as f64 / 1_000_000_000.0;

    // Convert to oscillations
    let oscillations = (total_seconds * OSCILLATIONS_PER_SECOND as f64).round() as u64;

    EagleTime::from_oscillations(oscillations)
}

/// Get current Eagle Time as oscillation count
///
/// Returns the number of hydrogen-1 hyperfine oscillations since the Apollo 11 landing
/// at picosecond precision.
pub fn eagle_time_now() -> EagleTime {
    datetime_to_eagle_time(Utc::now())
}

/// Get current Eagle Time as oscillations (integer, 704ps precision)
///
/// Returns the oscillation count since Apollo 11 landing.
/// Preferred method for timestamps - preserves full precision.
pub fn eagle_time_oscillations() -> u64 {
    eagle_time_now().oscillations().unwrap_or(0) as u64
}

/// Get current Eagle Time as nanosecond-precision f64 seconds (for compatibility)
///
/// Note: This loses the picosecond precision available in the oscillation count.
/// Prefer `eagle_time_oscillations()` for maximum precision.
#[deprecated(note = "Use eagle_time_oscillations() for integer timestamps")]
pub fn eagle_time_nanos() -> f64 {
    eagle_time_now().to_seconds_f64()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_oscillations_per_second_constant() {
        // Verify the hydrogen-1 21cm line frequency
        assert_eq!(OSCILLATIONS_PER_SECOND, 1_420_407_826);
    }

    #[test]
    fn test_eagle_epoch() {
        let epoch = Utc.with_ymd_and_hms(1969, 7, 20, 20, 17, 40).unwrap();
        let et = datetime_to_eagle_time(epoch);

        // At epoch, oscillation count should be zero
        assert_eq!(et.oscillations(), Some(0));

        let back = et.to_datetime();
        assert_eq!(epoch, back);
    }

    #[test]
    fn test_oscillation_counting() {
        // One second should be exactly OSCILLATIONS_PER_SECOND oscillations
        let one_second = EagleTime::from_oscillations(OSCILLATIONS_PER_SECOND as u64);
        assert_eq!(one_second.to_seconds_f64(), 1.0);

        // 100 seconds
        let hundred_seconds =
            EagleTime::from_oscillations(OSCILLATIONS_PER_SECOND * 100);
        assert_eq!(hundred_seconds.to_seconds_f64(), 100.0);
    }

    #[test]
    fn test_picosecond_precision() {
        // One oscillation ≈ 704.032 picoseconds
        let one_osc = EagleTime::from_oscillations(1);
        let ps = one_osc.picoseconds().unwrap();
        assert_eq!(ps, 704); // 704.032 truncated

        // Ten thousand oscillations
        let ten_k = EagleTime::from_oscillations(10_000);
        let ps = ten_k.picoseconds().unwrap();
        assert_eq!(ps, 7_040_320);
    }

    #[test]
    fn test_float_to_oscillation_conversion() {
        // Converting from seconds should preserve precision at oscillation level
        let et = EagleTime::from_seconds_f64(1.0);
        assert_eq!(et.oscillations(), Some(OSCILLATIONS_PER_SECOND as i64));

        // Round trip
        let seconds = et.to_seconds_f64();
        assert!((seconds - 1.0).abs() < 1e-9);
    }

    #[test]
    fn test_eagle_time_positive() {
        let future = Utc.with_ymd_and_hms(2025, 10, 25, 0, 0, 0).unwrap();
        let et = datetime_to_eagle_time(future);
        let back = et.to_datetime();

        // Should be exact at second precision
        assert_eq!((future - back).num_seconds().abs(), 0);
    }

    #[test]
    fn test_eagle_time_comparison() {
        let time1 = EagleTime::from_oscillations(1000);
        let time2 = EagleTime::from_oscillations(2000);
        let time3 = EagleTime::from_oscillations(1000);

        // Test ordering
        assert!(time1 < time2);
        assert!(time2 > time1);

        // Test equality at oscillation level
        assert_eq!(time1, time3);

        // Test with float (should compare correctly despite type difference)
        let time_f = EagleTime::new(EtType::f6(1000.0 / OSCILLATIONS_PER_SECOND as f64));
        assert_eq!(time1, time_f);
    }

    #[test]
    fn test_eagle_time_sorting() {
        let mut times = vec![
            EagleTime::from_oscillations(3000),
            EagleTime::from_oscillations(1000),
            EagleTime::from_oscillations_signed(2000),
            EagleTime::from_oscillations(500),
        ];

        times.sort();

        assert_eq!(times[0].oscillations(), Some(500));
        assert_eq!(times[1].oscillations(), Some(1000));
        assert_eq!(times[2].oscillations(), Some(2000));
        assert_eq!(times[3].oscillations(), Some(3000));
    }

    #[test]
    fn test_range_limits() {
        // u64 should give us ~411 years
        // From 1969 to 2380 is 411 years
        let year_2380 = Utc.with_ymd_and_hms(2380, 1, 1, 0, 0, 0).unwrap();
        let et = datetime_to_eagle_time(year_2380);

        // Should be within u64 range
        if let Some(osc) = et.oscillations_u64() {
            assert!(osc > 0);
            // ~411 years * 365.25 days * 86400 seconds * 1.42e9 oscillations/sec
            // Should be less than u64::MAX
        }
    }

    #[test]
    fn test_signed_oscillations() {
        // Test negative time deltas
        let before_epoch = EagleTime::from_oscillations_signed(-1000);
        assert_eq!(before_epoch.oscillations(), Some(-1000));

        // Should handle comparison with positive values
        let after_epoch = EagleTime::from_oscillations(500);
        assert!(before_epoch < after_epoch);
    }
}