1use crate::error::{Error, Result};
7use dvb_common::{Parse, Serialize};
8
9pub const TABLE_ID: u8 = 0x70;
11pub const PID: u16 = 0x0014;
13
14const HEADER_LEN: usize = 3;
15const UTC_TIME_LEN: usize = 5;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub struct TdtSection {
21 pub(crate) utc_time_raw: [u8; 5],
25}
26
27impl TdtSection {
28 #[must_use]
34 pub fn utc_time_decoded(&self) -> Option<dvb_common::time::MjdBcdDateTime> {
35 dvb_common::time::decode_mjd_bcd(self.utc_time_raw)
36 }
37
38 pub fn set_utc_time_decoded(&mut self, dt: dvb_common::time::MjdBcdDateTime) -> Result<()> {
44 self.utc_time_raw = dvb_common::time::encode_mjd_bcd(dt).ok_or(Error::ValueOutOfRange {
45 field: "TdtSection::utc_time",
46 reason: "date not representable in 16-bit MJD",
47 })?;
48 Ok(())
49 }
50
51 #[must_use]
53 pub fn utc_time_raw(&self) -> [u8; 5] {
54 self.utc_time_raw
55 }
56
57 #[must_use]
59 pub fn new(utc_time_raw: [u8; 5]) -> Self {
60 Self { utc_time_raw }
61 }
62}
63
64#[cfg(feature = "chrono")]
65impl TdtSection {
66 #[must_use]
70 pub fn utc_time(&self) -> Option<chrono::DateTime<chrono::Utc>> {
71 dvb_common::time::decode_mjd_bcd_utc(self.utc_time_raw)
72 }
73
74 pub fn set_utc_time(&mut self, utc_time: chrono::DateTime<chrono::Utc>) -> Result<()> {
80 self.utc_time_raw =
81 dvb_common::time::encode_mjd_bcd_utc(utc_time).ok_or(Error::ValueOutOfRange {
82 field: "TdtSection::utc_time",
83 reason: "date not representable in 16-bit MJD",
84 })?;
85 Ok(())
86 }
87}
88
89impl<'a> Parse<'a> for TdtSection {
90 type Error = crate::error::Error;
91 fn parse(bytes: &'a [u8]) -> Result<Self> {
92 let min_len = HEADER_LEN + UTC_TIME_LEN;
93 if bytes.len() < min_len {
94 return Err(Error::BufferTooShort {
95 need: min_len,
96 have: bytes.len(),
97 what: "TdtSection",
98 });
99 }
100 if bytes[0] != TABLE_ID {
101 return Err(Error::UnexpectedTableId {
102 table_id: bytes[0],
103 what: "TdtSection",
104 expected: &[TABLE_ID],
105 });
106 }
107 let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
108 if section_length as usize != UTC_TIME_LEN {
109 return Err(Error::SectionLengthOverflow {
110 declared: section_length as usize,
111 available: UTC_TIME_LEN,
112 });
113 }
114 let utc_time_raw = [bytes[3], bytes[4], bytes[5], bytes[6], bytes[7]];
115 Ok(TdtSection { utc_time_raw })
116 }
117}
118
119impl Serialize for TdtSection {
120 type Error = crate::error::Error;
121 fn serialized_len(&self) -> usize {
122 HEADER_LEN + UTC_TIME_LEN
123 }
124 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
125 let len = self.serialized_len();
126 if buf.len() < len {
127 return Err(Error::OutputBufferTooSmall {
128 need: len,
129 have: buf.len(),
130 });
131 }
132 buf[0] = TABLE_ID;
133 buf[1] = super::SECTION_B1_FLAGS_SHORT | ((UTC_TIME_LEN as u16 >> 8) as u8 & 0x0F);
134 buf[2] = UTC_TIME_LEN as u8;
135 buf[3..8].copy_from_slice(&self.utc_time_raw);
136 Ok(len)
137 }
138}
139impl<'a> crate::traits::TableDef<'a> for TdtSection {
140 const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
141 const NAME: &'static str = "TIME_AND_DATE";
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn parse_extracts_utc_time_raw() {
150 let bytes = [TABLE_ID, 0x70, 0x05, 0xE4, 0x09, 0x12, 0x34, 0x56];
151 let tdt = TdtSection::parse(&bytes).unwrap();
152 assert_eq!(tdt.utc_time_raw(), [0xE4, 0x09, 0x12, 0x34, 0x56]);
153 }
154
155 #[test]
156 fn parse_rejects_wrong_tag() {
157 let bytes = [0x71, 0x70, 0x05, 0, 0, 0, 0, 0];
158 assert!(matches!(
159 TdtSection::parse(&bytes).unwrap_err(),
160 Error::UnexpectedTableId { table_id: 0x71, .. }
161 ));
162 }
163
164 #[test]
165 fn parse_rejects_wrong_section_length() {
166 let bytes = [TABLE_ID, 0x70, 0x04, 0, 0, 0, 0, 0];
167 assert!(matches!(
168 TdtSection::parse(&bytes).unwrap_err(),
169 Error::SectionLengthOverflow { .. }
170 ));
171 }
172
173 #[test]
174 fn serialize_round_trip() {
175 let tdt = TdtSection {
176 utc_time_raw: [0xE4, 0x09, 0x12, 0x34, 0x56],
177 };
178 let mut buf = vec![0u8; tdt.serialized_len()];
179 tdt.serialize_into(&mut buf).unwrap();
180 let re = TdtSection::parse(&buf).unwrap();
181 assert_eq!(tdt, re);
182 }
183
184 #[cfg(feature = "chrono")]
185 #[test]
186 fn utc_time_decodes_to_chrono() {
187 let tdt = TdtSection {
188 utc_time_raw: [0xEA, 0x19, 0x12, 0x34, 0x56],
189 };
190 let dt = tdt.utc_time();
191 assert!(dt.is_some());
192 }
193
194 #[test]
195 fn utc_time_decodes_without_chrono() {
196 let tdt = TdtSection {
197 utc_time_raw: [0xE4, 0x09, 0x12, 0x34, 0x56],
198 };
199 let dt = tdt.utc_time_decoded().unwrap();
200 assert_eq!(dt.year, 2018);
201 assert_eq!(dt.month, 9);
202 assert_eq!(dt.day, 16);
203 assert_eq!(dt.hour, 12);
204 assert_eq!(dt.minute, 34);
205 assert_eq!(dt.second, 56);
206 }
207}