1use crate::{Dt, DtErr, DtErrKind, Offset, Scale, TimeParts, an_err};
2
3impl TimeParts {
4 pub fn days_since_1958_to_gregorian(days_since_epoch: i64) -> (i64, u8, u8) {
7 let mut year = 1958i64;
8 let mut remaining = days_since_epoch;
9
10 while remaining >= 0 {
11 let days_in_year = if Dt::is_leap_year(year) { 366 } else { 365 };
12 if remaining < days_in_year {
13 break;
14 }
15 remaining -= days_in_year;
16 year += 1;
17 }
18
19 let month_days = if Dt::is_leap_year(year) {
20 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
21 } else {
22 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
23 };
24
25 let mut month = 0usize;
26 let mut d = remaining as u32;
27 while month < 12 {
28 let days_in_month = month_days[month];
29 if d < days_in_month {
30 break;
31 }
32 d -= days_in_month;
33 month += 1;
34 }
35
36 let day = d as u8 + 1;
37 (year, month as u8 + 1, day)
38 }
39
40 pub fn gregorian_to_days_since_1958(year: i64, month: u8, day: u8) -> i64 {
42 let mut days = 0i64;
43 let mut y = 1958i64;
44 while y < year {
45 days += if Dt::is_leap_year(y) { 366 } else { 365 };
46 y += 1;
47 }
48 let month_days = if Dt::is_leap_year(year) {
49 [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
50 } else {
51 [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
52 };
53 for mday in month_days.iter().take(month as usize - 1) {
54 days += *mday as i64;
55 }
56 days + (day as i64 - 1)
57 }
58
59 pub fn from_ccsds_ccs(input: &[u8]) -> Result<TimeParts, DtErr> {
89 if input.is_empty() {
90 return Err(an_err!(DtErrKind::Incomplete, "empty"));
91 }
92
93 let p1 = input[0];
94 let mut idx = 1usize;
95
96 if (p1 & 0b1000_0000) != 0 {
97 return Err(an_err!(
98 DtErrKind::InvalidInput,
99 "p-field ext. not supported"
100 ));
101 }
102
103 let code_id = (p1 >> 4) & 0b0111;
104 if code_id != 0b101 {
105 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
106 }
107
108 let is_doy = ((p1 >> 3) & 1) != 0;
109 let n_subsec = (p1 & 0b0000_0111) as usize;
110
111 if n_subsec > 6 {
112 return Err(an_err!(DtErrKind::InvalidItem, "subsecond count"));
113 }
114
115 let min_len = 1 + 2 + 2 + 3 + n_subsec;
116 if input.len() < min_len {
117 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
118 }
119
120 let bcd_byte = |b: u8| -> Result<u8, DtErr> {
121 let hi = b >> 4;
122 let lo = b & 0x0F;
123 if hi > 9 || lo > 9 {
124 Err(an_err!(DtErrKind::InvalidBytes, "invalid bcd digit"))
125 } else {
126 Ok(hi * 10 + lo)
127 }
128 };
129
130 let y1 = bcd_byte(input[idx])?;
132 let y2 = bcd_byte(input[idx + 1])?;
133 let year = (y1 as i64) * 100 + (y2 as i64);
134 idx += 2;
135
136 let (month, day, day_of_year) = if !is_doy {
138 let mo = bcd_byte(input[idx])?;
139 let d = bcd_byte(input[idx + 1])?;
140 idx += 2;
141
142 if !(1..=12).contains(&mo) {
143 return Err(an_err!(DtErrKind::OutOfRange, "month"));
144 }
145 if !(1..=31).contains(&d) {
146 return Err(an_err!(DtErrKind::OutOfRange, "day"));
147 }
148 (Some(mo), Some(d), None)
149 } else {
150 let d1 = bcd_byte(input[idx])?;
151 let d2 = bcd_byte(input[idx + 1])?;
152 idx += 2;
153 let doy = (d1 as u16) * 100 + (d2 as u16);
154
155 if doy == 0 || doy > 366 || (doy == 366 && !Dt::is_leap_year(year)) {
156 return Err(an_err!(DtErrKind::OutOfRange, "day of year"));
157 }
158 (None, None, Some(doy))
159 };
160
161 let hour = bcd_byte(input[idx])?;
163 let minute = bcd_byte(input[idx + 1])?;
164 let mut second = bcd_byte(input[idx + 2])?;
165 idx += 3;
166
167 if hour > 23 {
168 return Err(an_err!(DtErrKind::OutOfRange, "hour"));
169 }
170 if minute > 59 {
171 return Err(an_err!(DtErrKind::OutOfRange, "minute"));
172 }
173
174 let is_leap_second = second == 60;
175 if is_leap_second {
176 second = 59;
177 } else if second > 59 {
178 return Err(an_err!(DtErrKind::OutOfRange, "second"));
179 }
180
181 let mut frac_value: u128 = 0;
183 for _ in 0..n_subsec {
184 let b = input[idx];
185 let hi = (b >> 4) as u128;
186 let lo = (b & 0x0F) as u128;
187 if hi > 9 || lo > 9 {
188 return Err(an_err!(DtErrKind::InvalidBytes, "invalid subsecond bcd"));
189 }
190 frac_value = frac_value * 100 + hi * 10 + lo;
191 idx += 1;
192 }
193
194 let attos = if n_subsec == 0 {
195 0
196 } else {
197 let decimal_places = (2 * n_subsec) as u32;
198 let denom = 10u128.pow(decimal_places);
199 ((frac_value * 1_000_000_000_000_000_000u128) / denom) as u64
200 };
201
202 let mut pd = TimeParts {
203 year: Some(year),
204 month,
205 day,
206 day_of_year,
207 hour: Some(hour),
208 minute: Some(minute),
209 second: Some(second),
210 attos: Some(attos),
211 is_leap_second,
212 scale: Scale::UTC,
213 offset: Some(Offset::Utc),
214 ..TimeParts::default()
215 };
216
217 pd.finish(false)?;
218 Ok(pd)
219 }
220
221 pub fn from_ccsds_c(input: &[u8]) -> Result<TimeParts, DtErr> {
247 if input.is_empty() {
248 return Err(an_err!(DtErrKind::Incomplete, "empty"));
249 }
250
251 let p1 = input[0];
252 let mut idx = 1usize;
253
254 let extension = (p1 & 0b1000_0000) != 0;
255 let code_id = (p1 >> 4) & 0b0111;
256 if code_id != 0b001 {
257 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
258 }
259
260 let base_coarse = (((p1 >> 2) & 0b0011) as usize) + 1;
261 let base_frac = (p1 & 0b0011) as usize;
262
263 let (n_coarse, n_frac) = if extension {
264 if input.len() < 2 {
265 return Err(an_err!(DtErrKind::InvalidInput, "p-field too short"));
266 }
267 let p2 = input[1];
268 idx += 1;
269
270 if (p2 & 0b1000_0000) != 0 {
271 return Err(an_err!(
272 DtErrKind::InvalidInput,
273 "further p-field ext. not supported"
274 ));
275 }
276
277 let add_coarse = ((p2 >> 5) & 0b0000_0011) as usize;
278 let add_frac = ((p2 >> 2) & 0b0000_0111) as usize;
279
280 (base_coarse + add_coarse, base_frac + add_frac)
281 } else {
282 (base_coarse, base_frac)
283 };
284
285 if n_coarse == 0 || input.len() < idx + n_coarse + n_frac {
286 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
287 }
288
289 let mut coarse_sec: u64 = 0;
291 for _ in 0..n_coarse {
292 coarse_sec = (coarse_sec << 8) | u64::from(input[idx]);
293 idx += 1;
294 }
295
296 let mut frac_raw: u128 = 0;
298 for _ in 0..n_frac {
299 frac_raw = (frac_raw << 8) | u128::from(input[idx]);
300 idx += 1;
301 }
302
303 let frac_attos = if n_frac == 0 {
304 0
305 } else {
306 let denom = 1u128 << (8 * n_frac as u32);
307 ((frac_raw * 1_000_000_000_000_000_000u128) / denom) as u64
308 };
309
310 let days_since_epoch = (coarse_sec / 86400) as i64;
312 let sec_of_day = (coarse_sec % 86400) as i64;
313
314 let (year, month, day) = TimeParts::days_since_1958_to_gregorian(days_since_epoch);
315
316 let hour = (sec_of_day / 3600) as u8;
317 let minute = ((sec_of_day % 3600) / 60) as u8;
318 let second = (sec_of_day % 60) as u8;
319
320 let mut pd = TimeParts {
321 year: Some(year),
322 month: Some(month),
323 day: Some(day),
324 hour: Some(hour),
325 minute: Some(minute),
326 second: Some(second),
327 attos: Some(frac_attos),
328 scale: Scale::TAI,
329 offset: Some(Offset::Utc),
330 ..TimeParts::default()
331 };
332 pd.finish(false)?;
333 Ok(pd)
334 }
335
336 pub fn from_ccsds_d(input: &[u8]) -> Result<TimeParts, DtErr> {
366 if input.is_empty() {
367 return Err(an_err!(DtErrKind::Incomplete, "empty"));
368 }
369
370 let p1 = input[0];
371 let mut idx = 1usize;
372
373 let extension = (p1 & 0b1000_0000) != 0;
374 if extension {
375 if input.len() < 2 {
376 return Err(an_err!(DtErrKind::InvalidInput, "p-field too short"));
377 }
378 idx += 1;
379 }
380
381 let code_id = (p1 >> 4) & 0b0111;
382 if code_id != 0b100 {
383 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
384 }
385
386 if (p1 & 0b0000_1000) != 0 {
387 return Err(an_err!(
388 DtErrKind::InvalidItem,
389 "non-level-1 epoch not supported"
390 ));
391 }
392
393 let n_day = if (p1 & 0b0000_0100) == 0 { 2 } else { 3 };
394 let sub_ms_code = p1 & 0b0000_0011;
395
396 let n_subsec = match sub_ms_code {
397 0b00 => 0,
398 0b01 => 2,
399 0b10 => 4,
400 _ => return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code")),
401 };
402
403 if input.len() < idx + n_day + 4 + n_subsec {
404 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
405 }
406
407 let mut day_count: u64 = 0;
409 for _ in 0..n_day {
410 day_count = (day_count << 8) | u64::from(input[idx]);
411 idx += 1;
412 }
413
414 let mut millis_of_day: u64 = 0;
415 for _ in 0..4 {
416 millis_of_day = (millis_of_day << 8) | u64::from(input[idx]);
417 idx += 1;
418 }
419
420 let mut frac_raw: u64 = 0;
421 for _ in 0..n_subsec {
422 frac_raw = (frac_raw << 8) | u64::from(input[idx]);
423 idx += 1;
424 }
425
426 let total_sec_in_day = millis_of_day / 1000;
428 let is_leap_second = total_sec_in_day == 86400;
429
430 let effective_sec = if is_leap_second {
431 86399
432 } else {
433 total_sec_in_day
434 };
435
436 let sec_of_day = effective_sec;
437 let remaining_ms = (millis_of_day % 1000) as u128;
438
439 let sub_ms_attos = if n_subsec == 0 {
441 0
442 } else if sub_ms_code == 0b01 {
443 (frac_raw as u128 * 1_000_000_000_000_000) / 65_536
444 } else {
445 (frac_raw as u128 * 1_000_000_000_000_000_000) / (1u128 << 32)
446 };
447
448 let frac_attos = remaining_ms * 1_000_000_000_000_000 + sub_ms_attos;
449
450 let days_since_epoch = day_count as i64;
452 let (year, month, day) = TimeParts::days_since_1958_to_gregorian(days_since_epoch);
453
454 let hour = (sec_of_day / 3600) as u8;
455 let minute = ((sec_of_day % 3600) / 60) as u8;
456 let mut second = (sec_of_day % 60) as u8;
457
458 if is_leap_second {
459 second = 60;
460 }
461
462 let mut pd = TimeParts {
463 year: Some(year),
464 month: Some(month),
465 day: Some(day),
466 hour: Some(hour),
467 minute: Some(minute),
468 second: Some(second),
469 attos: Some(frac_attos as u64),
470 is_leap_second,
471 scale: Scale::UTC,
472 offset: Some(Offset::Utc),
473 ..TimeParts::default()
474 };
475 pd.finish(false)?;
476 Ok(pd)
477 }
478
479 pub fn from_ccsds_bin(input: &[u8]) -> Result<TimeParts, DtErr> {
494 if input.is_empty() {
495 return Err(an_err!(DtErrKind::Incomplete, "empty"));
496 }
497 let code_id = (input[0] >> 4) & 0b0111;
498 match code_id {
499 0b001 => Self::from_ccsds_c(input),
500 0b100 => Self::from_ccsds_d(input),
501 0b101 => Self::from_ccsds_ccs(input),
502 _ => Err(an_err!(DtErrKind::InvalidItem, "unknown code id")),
503 }
504 }
505}