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
//! Format wave 2 — additional forensic timestamp encodings decoded from their raw
//! fields/bytes. Each value→answer below is a SPEC worked example (tier-1): the
//! epoch, byte layout, and units are stated by the authoritative spec, so the
//! expected instant is derivable from the documented construction.
#![allow(clippy::unwrap_used, clippy::expect_used)]
use timeglyph::compose::{cp56time2a, ext4_extra, iso9660, oracle_date, udf};
#[test]
fn oracle_7byte_date_decodes_the_documented_layout() {
// Oracle DATE internal 7-byte: [century+100, year_of_century+100, month, day,
// hour+1, minute+1, second+1]. 2020-01-01 00:00:00 = [120,120,1,1,1,1,1].
let inst = oracle_date([120, 120, 1, 1, 1, 1, 1]).unwrap();
assert_eq!(inst.to_rfc3339().unwrap(), "2020-01-01T00:00:00Z");
// A time-of-day example: 2024-07-15 13:30:45 = century 20, year 24 → [120,124,...],
// hour 14, minute 31, second 46 (each +1).
let inst2 = oracle_date([120, 124, 7, 15, 14, 31, 46]).unwrap();
assert_eq!(inst2.to_rfc3339().unwrap(), "2024-07-15T13:30:45Z");
}
#[test]
fn iso9660_7byte_recording_date_decodes() {
// ECMA-119 §9.1.5: [years since 1900, month, day, hour, minute, second,
// offset from GMT in 15-minute intervals (signed)]. 2020-01-01 00:00:00 GMT.
let inst = iso9660([120, 1, 1, 0, 0, 0, 0]).unwrap();
assert_eq!(inst.to_rfc3339().unwrap(), "2020-01-01T00:00:00Z");
// A +2h offset (8 × 15 min) means the wall time is 2h ahead of UTC → the
// instant is 2h earlier: 10:00 at +02:00 = 08:00Z.
let inst2 = iso9660([120, 6, 15, 10, 0, 0, 8]).unwrap();
assert_eq!(inst2.to_rfc3339().unwrap(), "2020-06-15T08:00:00Z");
}
#[test]
fn ext4_extra_extends_epoch_and_carries_nanoseconds() {
// ext4: 32-bit seconds since 1970 + a 32-bit `extra` (low 2 bits extend the
// epoch by ×2^32 s; high 30 bits are nanoseconds). extra=0 → the plain second.
assert_eq!(
ext4_extra(1_577_836_800, 0).to_rfc3339().unwrap(),
"2020-01-01T00:00:00Z"
);
// 500 ms rides in extra>>2: extra = 500_000_000 << 2.
assert!(ext4_extra(1_577_836_800, 500_000_000 << 2)
.to_rfc3339()
.unwrap()
.starts_with("2020-01-01T00:00:00.5"));
}
#[test]
fn cp56time2a_iec60870_decodes() {
// IEC 60870-5 CP56Time2a, 7 bytes: [ms LE u16, minute+flags, hour+flags,
// day+dayOfWeek, month, year(+2000)]. 2020-01-01 00:00:00.000.
let inst = cp56time2a([0, 0, 0, 0, 1, 1, 20]).unwrap();
assert_eq!(inst.to_rfc3339().unwrap(), "2020-01-01T00:00:00Z");
// 2024-07-15 13:30:45.500: ms = 45500 = 0xB1BC → [0xBC,0xB1]; the flag bits in
// the minute/hour/day/year bytes must be masked off.
let inst2 = cp56time2a([0xBC, 0xB1, 0x80 | 0x1E, 0x80 | 0x0D, 0xE0 | 0x0F, 7, 24]).unwrap();
assert!(inst2
.to_rfc3339()
.unwrap()
.starts_with("2024-07-15T13:30:45.5"));
}
#[test]
fn udf_ecma167_timestamp_decodes() {
// ECMA-167 §7.3, 12 bytes: [TypeAndTimezone LE u16, year LE i16, month, day,
// hour, minute, second, centiseconds, hundreds-of-micros, micros].
// 2020-01-01 00:00:00 UTC, tz 0: year 2020 = 0x07E4 → [0xE4,0x07].
let inst = udf([0x00, 0x10, 0xE4, 0x07, 1, 1, 0, 0, 0, 0, 0, 0]).unwrap();
assert_eq!(inst.to_rfc3339().unwrap(), "2020-01-01T00:00:00Z");
// tz +120 min (0x078) means wall time is 2h ahead → instant 2h earlier.
let inst2 = udf([0x78, 0x10, 0xE4, 0x07, 6, 15, 10, 0, 0, 0, 0, 0]).unwrap();
assert_eq!(inst2.to_rfc3339().unwrap(), "2020-06-15T08:00:00Z");
}
#[test]
fn wave2_invalid_civil_fields_error_never_panic() {
// Month 13 is invalid: the civil builder must error (robustness), not panic.
assert!(oracle_date([120, 120, 13, 1, 1, 1, 1]).is_err());
assert!(iso9660([120, 13, 1, 0, 0, 0, 0]).is_err());
assert!(cp56time2a([0, 0, 0, 0, 1, 13, 20]).is_err());
assert!(udf([0x00, 0x10, 0xE4, 0x07, 13, 1, 0, 0, 0, 0, 0, 0]).is_err());
}