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_yr(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_yr(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_yr(y) { 366 } else { 365 };
46 y += 1;
47 }
48 let month_days = if Dt::is_leap_yr(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> {
93 if input.is_empty() {
94 return Err(an_err!(DtErrKind::Incomplete, "empty"));
95 }
96
97 let p1 = input[0];
98 let mut idx = 1usize;
99
100 if (p1 & 0b1000_0000) != 0 {
101 return Err(an_err!(
102 DtErrKind::InvalidInput,
103 "p-field ext. not supported"
104 ));
105 }
106
107 let code_id = (p1 >> 4) & 0b0111;
108 if code_id != 0b101 {
109 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
110 }
111
112 let is_doy = ((p1 >> 3) & 1) != 0;
113 let n_subsec = (p1 & 0b0000_0111) as usize;
114
115 if n_subsec > 6 {
116 return Err(an_err!(DtErrKind::InvalidItem, "subsecond count"));
117 }
118
119 let min_len = 1 + 2 + 2 + 3 + n_subsec;
120 if input.len() < min_len {
121 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
122 }
123
124 let bcd_byte = |b: u8| -> Result<u8, DtErr> {
125 let hi = b >> 4;
126 let lo = b & 0x0F;
127 if hi > 9 || lo > 9 {
128 Err(an_err!(DtErrKind::InvalidBytes, "invalid bcd digit"))
129 } else {
130 Ok(hi * 10 + lo)
131 }
132 };
133
134 let y1 = bcd_byte(input[idx])?;
136 let y2 = bcd_byte(input[idx + 1])?;
137 let year = (y1 as i64) * 100 + (y2 as i64);
138 idx += 2;
139
140 let (month, day, day_of_year) = if !is_doy {
142 let mo = bcd_byte(input[idx])?;
143 let d = bcd_byte(input[idx + 1])?;
144 idx += 2;
145
146 if !(1..=12).contains(&mo) {
147 return Err(an_err!(DtErrKind::OutOfRange, "month"));
148 }
149 if !(1..=31).contains(&d) {
150 return Err(an_err!(DtErrKind::OutOfRange, "day"));
151 }
152 (Some(mo), Some(d), None)
153 } else {
154 let d1 = bcd_byte(input[idx])?;
155 let d2 = bcd_byte(input[idx + 1])?;
156 idx += 2;
157 let doy = (d1 as u16) * 100 + (d2 as u16);
158
159 if doy == 0 || doy > 366 || (doy == 366 && !Dt::is_leap_yr(year)) {
160 return Err(an_err!(DtErrKind::OutOfRange, "day of year"));
161 }
162 (None, None, Some(doy))
163 };
164
165 let hour = bcd_byte(input[idx])?;
167 let minute = bcd_byte(input[idx + 1])?;
168 let mut second = bcd_byte(input[idx + 2])?;
169 idx += 3;
170
171 if hour > 23 {
172 return Err(an_err!(DtErrKind::OutOfRange, "hour"));
173 }
174 if minute > 59 {
175 return Err(an_err!(DtErrKind::OutOfRange, "minute"));
176 }
177
178 let is_leap_second = second == 60;
179 if is_leap_second {
180 second = 59;
181 } else if second > 59 {
182 return Err(an_err!(DtErrKind::OutOfRange, "second"));
183 }
184
185 let mut frac_value: u128 = 0;
187 for _ in 0..n_subsec {
188 let b = input[idx];
189 let hi = (b >> 4) as u128;
190 let lo = (b & 0x0F) as u128;
191 if hi > 9 || lo > 9 {
192 return Err(an_err!(DtErrKind::InvalidBytes, "invalid subsecond bcd"));
193 }
194 frac_value = frac_value * 100 + hi * 10 + lo;
195 idx += 1;
196 }
197
198 let attos = if n_subsec == 0 {
199 0
200 } else {
201 let decimal_places = (2 * n_subsec) as u32;
202 let denom = 10u128.pow(decimal_places);
203 ((frac_value * 1_000_000_000_000_000_000u128) / denom) as u64
204 };
205
206 let mut pd = TimeParts {
207 yr: Some(year),
208 mo: month,
209 day,
210 day_of_yr: day_of_year,
211 hr: Some(hour),
212 min: Some(minute),
213 sec: Some(second),
214 attos: Some(attos),
215 is_leap_sec: is_leap_second,
216 scale: Scale::UTC,
217 offset: Some(Offset::Utc),
218 ..TimeParts::default()
219 };
220
221 pd.finish(false)?;
222 Ok(pd)
223 }
224
225 pub fn from_ccsds_c(input: &[u8]) -> Result<TimeParts, DtErr> {
255 if input.is_empty() {
256 return Err(an_err!(DtErrKind::Incomplete, "empty"));
257 }
258
259 let p1 = input[0];
260 let mut idx = 1usize;
261
262 let extension = (p1 & 0b1000_0000) != 0;
263 let code_id = (p1 >> 4) & 0b0111;
264 if code_id != 0b001 {
265 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
266 }
267
268 let base_coarse = (((p1 >> 2) & 0b0011) as usize) + 1;
269 let base_frac = (p1 & 0b0011) as usize;
270
271 let (n_coarse, n_frac) = if extension {
272 if input.len() < 2 {
273 return Err(an_err!(DtErrKind::InvalidInput, "p-field too short"));
274 }
275 let p2 = input[1];
276 idx += 1;
277
278 if (p2 & 0b1000_0000) != 0 {
279 return Err(an_err!(
280 DtErrKind::InvalidInput,
281 "further p-field ext. not supported"
282 ));
283 }
284
285 let add_coarse = ((p2 >> 5) & 0b0000_0011) as usize;
286 let add_frac = ((p2 >> 2) & 0b0000_0111) as usize;
287
288 (base_coarse + add_coarse, base_frac + add_frac)
289 } else {
290 (base_coarse, base_frac)
291 };
292
293 if n_coarse == 0 || input.len() < idx + n_coarse + n_frac {
294 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
295 }
296
297 let mut coarse_sec: u64 = 0;
299 for _ in 0..n_coarse {
300 coarse_sec = (coarse_sec << 8) | u64::from(input[idx]);
301 idx += 1;
302 }
303
304 let mut frac_raw: u128 = 0;
306 for _ in 0..n_frac {
307 frac_raw = (frac_raw << 8) | u128::from(input[idx]);
308 idx += 1;
309 }
310
311 let frac_attos = if n_frac == 0 {
312 0
313 } else {
314 let denom = 1u128 << (8 * n_frac as u32);
315 ((frac_raw * 1_000_000_000_000_000_000u128) / denom) as u64
316 };
317
318 let days_since_epoch = (coarse_sec / 86400) as i64;
320 let sec_of_day = (coarse_sec % 86400) as i64;
321
322 let (year, month, day) = TimeParts::days_since_1958_to_gregorian(days_since_epoch);
323
324 let hour = (sec_of_day / 3600) as u8;
325 let minute = ((sec_of_day % 3600) / 60) as u8;
326 let second = (sec_of_day % 60) as u8;
327
328 let mut pd = TimeParts {
329 yr: Some(year),
330 mo: Some(month),
331 day: Some(day),
332 hr: Some(hour),
333 min: Some(minute),
334 sec: Some(second),
335 attos: Some(frac_attos),
336 scale: Scale::TAI,
337 offset: Some(Offset::Utc),
338 ..TimeParts::default()
339 };
340 pd.finish(false)?;
341 Ok(pd)
342 }
343
344 pub fn from_ccsds_d(input: &[u8]) -> Result<TimeParts, DtErr> {
379 if input.is_empty() {
380 return Err(an_err!(DtErrKind::Incomplete, "empty"));
381 }
382
383 let p1 = input[0];
384 let mut idx = 1usize;
385
386 let extension = (p1 & 0b1000_0000) != 0;
387 if extension {
388 if input.len() < 2 {
389 return Err(an_err!(DtErrKind::InvalidInput, "p-field too short"));
390 }
391 idx += 1;
392 }
393
394 let code_id = (p1 >> 4) & 0b0111;
395 if code_id != 0b100 {
396 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
397 }
398
399 if (p1 & 0b0000_1000) != 0 {
400 return Err(an_err!(
401 DtErrKind::InvalidItem,
402 "non-level-1 epoch not supported"
403 ));
404 }
405
406 let n_day = if (p1 & 0b0000_0100) == 0 { 2 } else { 3 };
407 let sub_ms_code = p1 & 0b0000_0011;
408
409 let n_subsec = match sub_ms_code {
410 0b00 => 0,
411 0b01 => 2,
412 0b10 => 4,
413 _ => return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code")),
414 };
415
416 if input.len() < idx + n_day + 4 + n_subsec {
417 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
418 }
419
420 let mut day_count: u64 = 0;
422 for _ in 0..n_day {
423 day_count = (day_count << 8) | u64::from(input[idx]);
424 idx += 1;
425 }
426
427 let mut millis_of_day: u64 = 0;
428 for _ in 0..4 {
429 millis_of_day = (millis_of_day << 8) | u64::from(input[idx]);
430 idx += 1;
431 }
432
433 let mut frac_raw: u64 = 0;
434 for _ in 0..n_subsec {
435 frac_raw = (frac_raw << 8) | u64::from(input[idx]);
436 idx += 1;
437 }
438
439 let total_sec_in_day = millis_of_day / 1000;
441 let is_leap_second = total_sec_in_day == 86400;
442
443 let effective_sec = if is_leap_second {
444 86399
445 } else {
446 total_sec_in_day
447 };
448
449 let sec_of_day = effective_sec;
450 let remaining_ms = (millis_of_day % 1000) as u128;
451
452 let sub_ms_attos = if n_subsec == 0 {
454 0
455 } else if sub_ms_code == 0b01 {
456 (frac_raw as u128 * 1_000_000_000_000_000) / 65_536
457 } else {
458 (frac_raw as u128 * 1_000_000_000_000_000_000) / (1u128 << 32)
459 };
460
461 let frac_attos = remaining_ms * 1_000_000_000_000_000 + sub_ms_attos;
462
463 let days_since_epoch = day_count as i64;
465 let (year, month, day) = TimeParts::days_since_1958_to_gregorian(days_since_epoch);
466
467 let hour = (sec_of_day / 3600) as u8;
468 let minute = ((sec_of_day % 3600) / 60) as u8;
469 let mut second = (sec_of_day % 60) as u8;
470
471 if is_leap_second {
472 second = 60;
473 }
474
475 let mut pd = TimeParts {
476 yr: Some(year),
477 mo: Some(month),
478 day: Some(day),
479 hr: Some(hour),
480 min: Some(minute),
481 sec: Some(second),
482 attos: Some(frac_attos as u64),
483 is_leap_sec: is_leap_second,
484 scale: Scale::UTC,
485 offset: Some(Offset::Utc),
486 ..TimeParts::default()
487 };
488 pd.finish(false)?;
489 Ok(pd)
490 }
491
492 pub fn from_ccsds_bin(input: &[u8]) -> Result<TimeParts, DtErr> {
508 if input.is_empty() {
509 return Err(an_err!(DtErrKind::Incomplete, "empty"));
510 }
511 let code_id = (input[0] >> 4) & 0b0111;
512 match code_id {
513 0b001 => Self::from_ccsds_c(input),
514 0b100 => Self::from_ccsds_d(input),
515 0b101 => Self::from_ccsds_ccs(input),
516 _ => Err(an_err!(DtErrKind::InvalidItem, "unknown code id")),
517 }
518 }
519}