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 if second == 60 {
179 second = 59;
180 } else if second > 59 {
181 return Err(an_err!(DtErrKind::OutOfRange, "second"));
182 }
183
184 let mut frac_value: u128 = 0;
186 for _ in 0..n_subsec {
187 let b = input[idx];
188 let hi = (b >> 4) as u128;
189 let lo = (b & 0x0F) as u128;
190 if hi > 9 || lo > 9 {
191 return Err(an_err!(DtErrKind::InvalidBytes, "invalid subsecond bcd"));
192 }
193 frac_value = frac_value * 100 + hi * 10 + lo;
194 idx += 1;
195 }
196
197 let attos = if n_subsec == 0 {
198 0
199 } else {
200 let decimal_places = (2 * n_subsec) as u32;
201 let denom = 10u128.pow(decimal_places);
202 ((frac_value * 1_000_000_000_000_000_000u128) / denom) as u64
203 };
204
205 let mut pd = TimeParts {
206 yr: Some(year),
207 mo: month,
208 day,
209 day_of_yr: day_of_year,
210 hr: hour,
211 min: minute,
212 sec: second,
213 attos: attos,
214 scale: Scale::UTC,
215 offset: Some(Offset::Utc),
216 ..TimeParts::default()
217 };
218
219 pd.finish(false)?;
220 Ok(pd)
221 }
222
223 pub fn from_ccsds_c(input: &[u8]) -> Result<TimeParts, DtErr> {
253 if input.is_empty() {
254 return Err(an_err!(DtErrKind::Incomplete, "empty"));
255 }
256
257 let p1 = input[0];
258 let mut idx = 1usize;
259
260 let extension = (p1 & 0b1000_0000) != 0;
261 let code_id = (p1 >> 4) & 0b0111;
262 if code_id != 0b001 {
263 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
264 }
265
266 let base_coarse = (((p1 >> 2) & 0b0011) as usize) + 1;
267 let base_frac = (p1 & 0b0011) as usize;
268
269 let (n_coarse, n_frac) = if extension {
270 if input.len() < 2 {
271 return Err(an_err!(DtErrKind::InvalidInput, "p-field too short"));
272 }
273 let p2 = input[1];
274 idx += 1;
275
276 if (p2 & 0b1000_0000) != 0 {
277 return Err(an_err!(
278 DtErrKind::InvalidInput,
279 "further p-field ext. not supported"
280 ));
281 }
282
283 let add_coarse = ((p2 >> 5) & 0b0000_0011) as usize;
284 let add_frac = ((p2 >> 2) & 0b0000_0111) as usize;
285
286 (base_coarse + add_coarse, base_frac + add_frac)
287 } else {
288 (base_coarse, base_frac)
289 };
290
291 if n_coarse == 0 || input.len() < idx + n_coarse + n_frac {
292 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
293 }
294
295 let mut coarse_sec: u64 = 0;
297 for _ in 0..n_coarse {
298 coarse_sec = (coarse_sec << 8) | u64::from(input[idx]);
299 idx += 1;
300 }
301
302 let mut frac_raw: u128 = 0;
304 for _ in 0..n_frac {
305 frac_raw = (frac_raw << 8) | u128::from(input[idx]);
306 idx += 1;
307 }
308
309 let frac_attos = if n_frac == 0 {
310 0
311 } else {
312 let denom = 1u128 << (8 * n_frac as u32);
313 ((frac_raw * 1_000_000_000_000_000_000u128) / denom) as u64
314 };
315
316 let days_since_epoch = (coarse_sec / 86400) as i64;
318 let sec_of_day = (coarse_sec % 86400) as i64;
319
320 let (year, month, day) = TimeParts::days_since_1958_to_gregorian(days_since_epoch);
321
322 let hour = (sec_of_day / 3600) as u8;
323 let minute = ((sec_of_day % 3600) / 60) as u8;
324 let second = (sec_of_day % 60) as u8;
325
326 let mut pd = TimeParts {
327 yr: Some(year),
328 mo: Some(month),
329 day: Some(day),
330 hr: hour,
331 min: minute,
332 sec: second,
333 attos: frac_attos,
334 scale: Scale::TAI,
335 offset: Some(Offset::Utc),
336 ..TimeParts::default()
337 };
338 pd.finish(false)?;
339 Ok(pd)
340 }
341
342 pub fn from_ccsds_d(input: &[u8]) -> Result<TimeParts, DtErr> {
377 if input.is_empty() {
378 return Err(an_err!(DtErrKind::Incomplete, "empty"));
379 }
380
381 let p1 = input[0];
382 let mut idx = 1usize;
383
384 let extension = (p1 & 0b1000_0000) != 0;
385 if extension {
386 if input.len() < 2 {
387 return Err(an_err!(DtErrKind::InvalidInput, "p-field too short"));
388 }
389 idx += 1;
390 }
391
392 let code_id = (p1 >> 4) & 0b0111;
393 if code_id != 0b100 {
394 return Err(an_err!(DtErrKind::InvalidItem, "code id"));
395 }
396
397 if (p1 & 0b0000_1000) != 0 {
398 return Err(an_err!(
399 DtErrKind::InvalidItem,
400 "non-level-1 epoch not supported"
401 ));
402 }
403
404 let n_day = if (p1 & 0b0000_0100) == 0 { 2 } else { 3 };
405 let sub_ms_code = p1 & 0b0000_0011;
406
407 let n_subsec = match sub_ms_code {
408 0b00 => 0,
409 0b01 => 2,
410 0b10 => 4,
411 _ => return Err(an_err!(DtErrKind::InvalidItem, "sub-millisecond code")),
412 };
413
414 if input.len() < idx + n_day + 4 + n_subsec {
415 return Err(an_err!(DtErrKind::InvalidSyntax, "t-field too short"));
416 }
417
418 let mut day_count: u64 = 0;
420 for _ in 0..n_day {
421 day_count = (day_count << 8) | u64::from(input[idx]);
422 idx += 1;
423 }
424
425 let mut millis_of_day: u64 = 0;
426 for _ in 0..4 {
427 millis_of_day = (millis_of_day << 8) | u64::from(input[idx]);
428 idx += 1;
429 }
430
431 let mut frac_raw: u64 = 0;
432 for _ in 0..n_subsec {
433 frac_raw = (frac_raw << 8) | u64::from(input[idx]);
434 idx += 1;
435 }
436
437 let total_sec_in_day = millis_of_day / 1000;
439 let is_leap_second = total_sec_in_day == 86400;
440
441 let effective_sec = if is_leap_second {
442 86399
443 } else {
444 total_sec_in_day
445 };
446
447 let sec_of_day = effective_sec;
448 let remaining_ms = (millis_of_day % 1000) as u128;
449
450 let sub_ms_attos = if n_subsec == 0 {
452 0
453 } else if sub_ms_code == 0b01 {
454 (frac_raw as u128 * 1_000_000_000_000_000) / 65_536
455 } else {
456 (frac_raw as u128 * 1_000_000_000_000_000_000) / (1u128 << 32)
457 };
458
459 let frac_attos = remaining_ms * 1_000_000_000_000_000 + sub_ms_attos;
460
461 let days_since_epoch = day_count as i64;
463 let (year, month, day) = TimeParts::days_since_1958_to_gregorian(days_since_epoch);
464
465 let hour = (sec_of_day / 3600) as u8;
466 let minute = ((sec_of_day % 3600) / 60) as u8;
467 let mut second = (sec_of_day % 60) as u8;
468
469 if is_leap_second {
470 second = 60;
471 }
472
473 let mut pd = TimeParts {
474 yr: Some(year),
475 mo: Some(month),
476 day: Some(day),
477 hr: hour,
478 min: minute,
479 sec: second,
480 attos: frac_attos as u64,
481 scale: Scale::UTC,
482 offset: Some(Offset::Utc),
483 ..TimeParts::default()
484 };
485 pd.finish(false)?;
486 Ok(pd)
487 }
488
489 pub fn from_ccsds_bin(input: &[u8]) -> Result<TimeParts, DtErr> {
505 if input.is_empty() {
506 return Err(an_err!(DtErrKind::Incomplete, "empty"));
507 }
508 let code_id = (input[0] >> 4) & 0b0111;
509 match code_id {
510 0b001 => Self::from_ccsds_c(input),
511 0b100 => Self::from_ccsds_d(input),
512 0b101 => Self::from_ccsds_ccs(input),
513 _ => Err(an_err!(DtErrKind::InvalidItem, "unknown code id")),
514 }
515 }
516}