gpsd_json/protocol/v3/response.rs
1//! GPSD Protocol v3 response message types
2//!
3//! This module defines all the response messages that GPSD can send to clients.
4//! Each message type corresponds to a specific class of GPS data or status information.
5//!
6//! Response messages are identified by their "class" field in the JSON structure.
7//! Common message types include:
8//! - TPV (Time-Position-Velocity): Core GPS fix data
9//! - SKY: Satellite visibility and signal strength
10//! - GST: GPS pseudorange error statistics
11//! - ATT: Attitude/orientation data
12//! - DEVICE/DEVICES: GPS receiver information
13//! - VERSION: GPSD daemon version information
14//!
15//! All timestamps use the ISO 8601 format and are represented as `DateTime<Utc>`.
16
17use chrono::{DateTime, Utc};
18use serde::Deserialize;
19
20use super::types::*;
21
22/// Time-Position-Velocity (TPV) report
23///
24/// The TPV message is the core GPS fix report, containing time, position, and velocity data.
25/// This is the primary message type for navigation applications.
26///
27/// Reference: [json_tpv_read](https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c?ref_type=heads#L34)
28#[derive(Debug, Clone, PartialEq, Deserialize)]
29pub struct Tpv {
30 /// Altitude in meters (deprecated, use altMSL or altHAE)
31 pub alt: Option<f64>,
32 /// Altitude, height above ellipsoid, in meters
33 #[serde(rename = "altHAE")]
34 pub alt_hae: Option<f64>,
35 /// Altitude, MSL (mean sea level) in meters
36 #[serde(rename = "altMSL")]
37 pub alt_msl: Option<f64>,
38 /// Antenna status (OK, OPEN, SHORT)
39 pub ant: Option<AntennaStatus>,
40 /// RTK baseline information (flattened)
41 #[serde(flatten)]
42 pub base: Baseline,
43 /// Climb/sink rate in meters per second
44 pub climb: Option<f64>,
45 /// Geodetic datum (usually WGS84)
46 pub datum: Option<String>,
47 /// Device path that provided this data
48 pub device: Option<String>,
49 /// Depth below mean sea level in meters
50 pub depth: Option<f64>,
51 /// Age of DGPS corrections in seconds
52 #[serde(rename = "dgpsAge")]
53 pub dgps_age: Option<f64>,
54 /// DGPS station ID
55 #[serde(rename = "dgpsSta")]
56 pub dgps_sta: Option<i32>,
57 /// ECEF coordinates and velocities (flattened)
58 #[serde(flatten)]
59 pub ecef: Ecef,
60 /// Estimated climb error in meters/second
61 pub epc: Option<f64>,
62 /// Estimated track error in degrees
63 pub epd: Option<f64>,
64 /// Estimated horizontal position error in meters
65 pub eph: Option<f64>,
66 /// Estimated speed error in meters/second
67 pub eps: Option<f64>,
68 /// Estimated time error in seconds
69 pub ept: Option<f64>,
70 /// Longitude error estimate in meters
71 pub epx: Option<f64>,
72 /// Latitude error estimate in meters
73 pub epy: Option<f64>,
74 /// Estimated vertical error in meters
75 pub epv: Option<f64>,
76 /// Geoid separation (height of geoid above WGS84 ellipsoid) in meters
77 #[serde(rename = "geoidSep")]
78 pub geoid_sep: Option<f64>,
79 /// Latitude in degrees (positive = North)
80 pub lat: Option<f64>,
81 /// Jamming indicator
82 pub jam: Option<i32>,
83 /// Current leap seconds (GPS-UTC offset)
84 pub leapseconds: Option<i32>,
85 /// Longitude in degrees (positive = East)
86 pub lon: Option<f64>,
87 /// Magnetic track (course over ground relative to magnetic north)
88 pub magtrack: Option<f64>,
89 /// Magnetic variation in degrees
90 pub magvar: Option<f64>,
91 /// GPS fix mode (NoFix, 2D, 3D)
92 pub mode: FixMode,
93 /// NED velocity components (flattened)
94 #[serde(flatten)]
95 pub ned: Ned,
96 /// Temperature in degrees Celsius
97 pub temp: Option<f64>,
98 /// GPS time of fix
99 pub time: Option<DateTime<Utc>>,
100 /// True track (course over ground) in degrees
101 pub track: Option<f64>,
102 /// Spherical error probability in meters
103 pub sep: Option<f64>,
104 /// Speed over ground in meters/second
105 pub speed: Option<f64>,
106 /// GPS fix status (standard, DGPS, RTK, etc.)
107 pub status: Option<FixStatus>,
108 /// Wind angle magnetic in degrees
109 pub wanglem: Option<f64>,
110 /// Wind angle relative in degrees
111 pub wangler: Option<f64>,
112 /// Wind angle true in degrees
113 pub wanglet: Option<f64>,
114 /// Wind speed relative in meters/second
115 pub wspeedr: Option<f64>,
116 /// Wind speed true in meters/second
117 pub wspeedt: Option<f64>,
118 /// Water temperature in degrees Celsius
119 pub wtemp: Option<f64>,
120 /// Reception time (when enabled by timing policy)
121 #[serde(rename = "rtime")]
122 #[serde(default, deserialize_with = "f64_to_datetime")]
123 pub rtime: Option<DateTime<Utc>>,
124 /// PPS edge time (when enabled by timing policy)
125 #[serde(default, deserialize_with = "f64_to_datetime")]
126 pub pps: Option<DateTime<Utc>>,
127 /// Start of response time (when enabled by timing policy)
128 #[serde(default, deserialize_with = "f64_to_datetime")]
129 pub sor: Option<DateTime<Utc>>,
130 /// Character count in the sentence
131 pub chars: Option<u64>,
132 /// Number of satellites used in solution
133 pub sats: Option<i32>,
134 /// GPS week number
135 pub week: Option<u16>,
136 /// GPS time of week in seconds
137 pub tow: Option<f64>,
138 /// GPS week rollover count
139 pub rollovers: Option<i32>,
140 #[cfg(feature = "extra-fields")]
141 /// Additional fields not explicitly defined
142 #[serde(flatten)]
143 extra: std::collections::HashMap<String, serde_json::Value>,
144}
145
146/// Satellite Sky View (SKY) report
147///
148/// The SKY message reports the satellites visible to the GPS receiver,
149/// including signal strength, elevation, azimuth, and usage status.
150#[derive(Debug, Clone, PartialEq, Deserialize)]
151pub struct Sky {
152 /// Device path that provided this data
153 pub device: Option<String>,
154 /// Dilution of precision values (flattened)
155 #[serde(flatten)]
156 pub dop: Dop,
157 /// GPS time of this sky view
158 pub time: Option<DateTime<Utc>>,
159 /// Number of satellites visible
160 #[serde(rename = "nSat")]
161 pub n_sat: Option<i32>,
162 /// Number of satellites used in navigation solution
163 #[serde(rename = "uSat")]
164 pub u_sat: Option<i32>,
165 /// List of visible satellites with their properties
166 pub satellites: Vec<Satellite>,
167 #[cfg(feature = "extra-fields")]
168 /// Additional fields not explicitly defined
169 #[serde(flatten)]
170 extra: std::collections::HashMap<String, serde_json::Value>,
171}
172
173/// GPS Pseudorange Error Statistics (GST)
174///
175/// The GST message provides GPS pseudorange noise statistics,
176/// including RMS values of standard deviation ranges.
177///
178/// Reference: [json_noise_read](https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c?ref_type=heads#L175)
179#[derive(Debug, Clone, PartialEq, Deserialize)]
180pub struct Gst {
181 /// Device path that provided this data
182 pub device: Option<String>,
183 /// GPS time of these statistics
184 pub time: Option<DateTime<Utc>>,
185 /// Altitude error in meters (1-sigma)
186 pub alt: Option<f64>,
187 /// Latitude error in meters (1-sigma)
188 pub lat: Option<f64>,
189 /// Longitude error in meters (1-sigma)
190 pub lon: Option<f64>,
191 /// Semi-major axis of error ellipse in meters
192 pub major: Option<f64>,
193 /// Semi-minor axis of error ellipse in meters
194 pub minor: Option<f64>,
195 /// Orientation of error ellipse in degrees from true north
196 pub orient: Option<f64>,
197 /// RMS value of standard deviation ranges
198 pub rms: Option<f64>,
199 /// East velocity error in meters/second (1-sigma)
200 pub ve: Option<f64>,
201 /// North velocity error in meters/second (1-sigma)
202 pub vn: Option<f64>,
203 /// Up velocity error in meters/second (1-sigma)
204 pub vu: Option<f64>,
205 #[cfg(feature = "extra-fields")]
206 /// Additional fields not explicitly defined
207 #[serde(flatten)]
208 extra: std::collections::HashMap<String, serde_json::Value>,
209}
210
211/// Attitude/orientation data
212///
213/// Reports the orientation of the device in 3D space.
214/// Currently a placeholder for future implementation.
215/// Reference: [json_att_read](https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c?ref_type=heads#L404)
216#[derive(Debug, Clone, PartialEq, Deserialize)]
217pub struct Attitude {
218 pub device: Option<String>,
219 pub acc_len: Option<f64>,
220 pub acc_x: Option<f64>,
221 pub acc_y: Option<f64>,
222 pub acc_z: Option<f64>,
223 #[serde(flatten)]
224 pub base: Baseline,
225 pub depth: Option<f64>,
226 pub dip: Option<f64>,
227 // pub gyro_temp: Option<f64>,
228 pub gyro_x: Option<f64>,
229 pub gyro_y: Option<f64>,
230 pub gyro_z: Option<f64>,
231 pub heading: Option<f64>,
232 pub mag_len: Option<f64>,
233 pub mag_x: Option<f64>,
234 pub mag_y: Option<f64>,
235 pub mag_z: Option<f64>,
236 pub mheading: Option<f64>,
237 pub msg: Option<String>,
238 pub pitch_st: Option<StatusCode>,
239 pub pitch: Option<f64>,
240 pub roll_st: Option<StatusCode>,
241 pub roll: Option<f64>,
242 pub temp: Option<f64>,
243 pub time: Option<DateTime<Utc>>,
244 #[serde(rename = "timeTag")]
245 pub time_tag: Option<String>,
246 pub yaw_st: Option<StatusCode>,
247 pub yaw: Option<f64>,
248 #[cfg(feature = "extra-fields")]
249 /// Additional fields not explicitly defined
250 #[serde(flatten)]
251 extra: std::collections::HashMap<String, serde_json::Value>,
252}
253
254/// Inertial Measurement Unit data
255///
256/// Reports accelerometer and gyroscope readings.
257/// Currently a placeholder for future implementation.
258/// Reference: [json_imu_read](https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c?ref_type=heads#L487)
259#[derive(Debug, Clone, PartialEq, Deserialize)]
260pub struct Imu {
261 pub device: Option<String>,
262 pub acc_len: Option<f64>,
263 pub acc_x: Option<f64>,
264 pub acc_y: Option<f64>,
265 pub acc_z: Option<f64>,
266 pub depth: Option<f64>,
267 pub dip: Option<f64>,
268 // pub gyro_temp: Option<f64>,
269 pub gyro_x: Option<f64>,
270 pub gyro_y: Option<f64>,
271 pub gyro_z: Option<f64>,
272 pub heading: Option<f64>,
273 pub mag_len: Option<f64>,
274 pub mag_x: Option<f64>,
275 pub mag_y: Option<f64>,
276 pub mag_z: Option<f64>,
277 pub mheading: Option<f64>,
278 pub msg: Option<String>,
279 pub pitch_st: Option<StatusCode>,
280 pub pitch: Option<f64>,
281 pub roll_st: Option<StatusCode>,
282 pub roll: Option<f64>,
283 pub temp: Option<f64>,
284 pub time: Option<DateTime<Utc>>,
285 #[serde(rename = "timeTag")]
286 pub time_tag: Option<String>,
287 pub yaw_st: Option<StatusCode>,
288 pub yaw: Option<f64>,
289 #[cfg(feature = "extra-fields")]
290 /// Additional fields not explicitly defined
291 #[serde(flatten)]
292 extra: std::collections::HashMap<String, serde_json::Value>,
293}
294
295/// Time Offset report
296///
297/// Reports the offset between system clock and GPS time.
298///
299/// Reference: [json_toff_read](https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c?ref_type=heads#L667)
300#[derive(Debug, Clone, PartialEq)]
301pub struct TimeOffset {
302 /// Device path that provided this data
303 pub device: Option<String>,
304 /// GPS time
305 pub real: Option<DateTime<Utc>>,
306 /// System clock time
307 pub clock: Option<DateTime<Utc>>,
308}
309
310impl<'de> Deserialize<'de> for TimeOffset {
311 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
312 where
313 D: serde::Deserializer<'de>,
314 {
315 #[derive(Debug, Deserialize)]
316 struct RawTimeOffset {
317 pub device: Option<String>,
318 pub real_sec: Option<i64>,
319 pub real_nsec: Option<i64>,
320 pub clock_sec: Option<i64>,
321 pub clock_nsec: Option<i64>,
322 }
323
324 let raw = RawTimeOffset::deserialize(deserializer)?;
325
326 Ok(TimeOffset {
327 device: raw.device,
328 real: deserialize_to_datetime(raw.real_sec, raw.real_nsec),
329 clock: deserialize_to_datetime(raw.clock_sec, raw.clock_nsec),
330 })
331 }
332}
333
334/// Pulse-Per-Second (PPS) timing report
335///
336/// Reports precise timing information from PPS-capable GPS receivers.
337#[derive(Debug, Clone, PartialEq)]
338pub struct Pps {
339 /// Device path that provided this data
340 pub device: Option<String>,
341 /// GPS time of PPS edge
342 pub real: Option<DateTime<Utc>>,
343 /// System clock time of PPS edge
344 pub clock: Option<DateTime<Utc>>,
345 /// Clock precision in nanoseconds
346 pub precision: Option<i32>,
347 /// Quantization error of PPS signal
348 pub q_err: Option<i32>,
349}
350
351impl<'de> Deserialize<'de> for Pps {
352 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
353 where
354 D: serde::Deserializer<'de>,
355 {
356 #[derive(Deserialize)]
357 struct RawPps {
358 pub device: Option<String>,
359 pub real_sec: Option<i64>,
360 pub real_nsec: Option<i64>,
361 pub clock_sec: Option<i64>,
362 pub clock_nsec: Option<i64>,
363 pub precision: Option<i32>,
364 #[serde(rename = "qErr")]
365 pub q_err: Option<i32>,
366 }
367
368 let raw = RawPps::deserialize(deserializer)?;
369 Ok(Pps {
370 device: raw.device,
371 real: deserialize_to_datetime(raw.real_sec, raw.real_nsec),
372 clock: deserialize_to_datetime(raw.clock_sec, raw.clock_nsec),
373 precision: raw.precision,
374 q_err: raw.q_err,
375 })
376 }
377}
378
379/// Oscillator/clock discipline status
380///
381/// Reports the status of the system's precision time reference.
382#[derive(Debug, Clone, PartialEq, Deserialize)]
383pub struct Oscillator {
384 /// Device path of the oscillator
385 pub device: String,
386 /// Whether the oscillator is running
387 pub running: bool,
388 /// Whether this is the reference clock
389 pub reference: bool,
390 /// Whether the clock is disciplined (synchronized)
391 pub disciplined: bool,
392 // delta: field commented out in original
393}
394
395/// GPSD daemon version information
396///
397/// Reports version and protocol information about the GPSD server.
398#[derive(Debug, Clone, PartialEq, Deserialize)]
399pub struct Version {
400 /// GPSD release version string
401 pub release: String,
402 /// Git revision hash
403 pub rev: String,
404 /// Protocol major version number
405 pub proto_major: i32,
406 /// Protocol minor version number
407 pub proto_minor: i32,
408 /// Remote server URL (if applicable)
409 pub remote: Option<String>,
410}
411
412/// List of GPS devices known to GPSD
413///
414/// Contains information about all GPS receivers connected to GPSD.
415#[derive(Debug, Clone, PartialEq)]
416pub struct DeviceList {
417 /// List of available GPS devices
418 pub devices: Vec<Device>,
419}
420
421impl<'de> Deserialize<'de> for DeviceList {
422 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
423 where
424 D: serde::Deserializer<'de>,
425 {
426 #[derive(Deserialize)]
427 struct RawSubDevice {
428 pub path: Option<String>,
429 pub activated: Option<serde_json::Value>,
430 pub flags: Option<PropertyFlags>,
431 pub driver: Option<String>,
432 pub hexdata: Option<String>,
433 pub sernum: Option<String>,
434 pub subtype: Option<String>,
435 pub subtype1: Option<String>,
436 pub native: Option<i32>,
437 pub bps: Option<i32>,
438 pub parity: Option<Parity>,
439 pub stopbits: Option<u32>,
440 pub cycle: Option<f64>,
441 pub mincycle: Option<f64>,
442 }
443
444 #[derive(Deserialize)]
445 struct RawDeviceList {
446 pub devices: Vec<RawSubDevice>,
447 }
448
449 let raw = RawDeviceList::deserialize(deserializer)?;
450
451 let mut devices = Vec::with_capacity(raw.devices.len());
452 for device in raw.devices.into_iter() {
453 let activated = device.activated;
454 let mut device = Device {
455 path: device.path,
456 activated: None,
457 flags: device.flags,
458 driver: device.driver,
459 hexdata: device.hexdata,
460 sernum: device.sernum,
461 subtype: device.subtype,
462 subtype1: device.subtype1,
463 native: device.native,
464 bps: device.bps,
465 parity: device.parity,
466 stopbits: device.stopbits,
467 cycle: device.cycle,
468 mincycle: device.mincycle,
469 };
470
471 match activated {
472 Some(serde_json::Value::String(iso_time)) => {
473 device.activated = DateTime::parse_from_rfc3339(&iso_time)
474 .ok()
475 .map(|dt| dt.with_timezone(&Utc));
476 }
477 Some(serde_json::Value::Number(unix_time)) => {
478 if let Some(secs) = unix_time.as_f64() {
479 device.activated = DateTime::<Utc>::from_timestamp(
480 secs.trunc() as i64,
481 ((secs.fract()) * 1e9) as u32,
482 )
483 }
484 }
485 Some(_) => {
486 return Err(serde::de::Error::custom(
487 "Invalid type for 'activated' field",
488 ));
489 }
490 None => {}
491 }
492
493 devices.push(device);
494 }
495
496 Ok(DeviceList { devices })
497 }
498}
499
500/// Poll response with current GPS state
501///
502/// Returns a snapshot of the current GPS fix data from all active devices.
503#[derive(Debug, Clone, PartialEq, Deserialize)]
504pub struct Poll {
505 /// Number of active devices
506 active: Option<i32>,
507 /// Timestamp of this poll
508 time: Option<DateTime<Utc>>,
509 /// TPV data from active devices
510 tpv: Vec<Tpv>,
511 /// GST data from active devices
512 gst: Vec<Gst>,
513 /// Sky view from active devices
514 sky: Vec<Sky>,
515}
516
517/// Error notification from GPSD
518///
519/// Reports errors that occur during GPSD operation.
520#[derive(Debug, Clone, PartialEq, Deserialize)]
521pub struct Error {
522 /// Error message text
523 pub message: String,
524}
525
526/// RTCM2 differential correction data
527///
528/// Real Time Correction Messages version 2.
529/// Currently a placeholder for future implementation.
530#[derive(Debug, Clone, PartialEq, Deserialize)]
531pub struct Rtcm2 {}
532
533/// RTCM3 differential correction data
534///
535/// Real Time Correction Messages version 3.
536/// Currently a placeholder for future implementation.
537#[derive(Debug, Clone, PartialEq, Deserialize)]
538pub struct Rtcm3 {}
539
540// https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c#L959
541// #[cfg(feature = "ais")]
542// #[derive(Debug, Clone, PartialEq, Deserialize)]
543// pub struct Aivdm {}
544
545/// Raw GPS receiver data
546///
547/// Contains raw measurement data from the GPS receiver,
548/// including pseudoranges, carrier phases, and signal strengths.
549///
550/// Reference: [json_raw_read](https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c#L219)
551#[derive(Debug, Clone, PartialEq)]
552pub struct Raw {
553 /// Device path that provided this data
554 pub device: Option<String>,
555 /// GPS time of these measurements
556 pub time: Option<DateTime<Utc>>,
557 /// Raw measurement data for each satellite
558 pub rawdata: Vec<Measurement>,
559}
560
561impl<'de> Deserialize<'de> for Raw {
562 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
563 where
564 D: serde::Deserializer<'de>,
565 {
566 #[derive(Deserialize)]
567 struct RawRaw {
568 pub device: Option<String>,
569 pub time: Option<f64>,
570 pub nsec: Option<f64>,
571 pub rawdata: Vec<Measurement>,
572 }
573
574 let raw = RawRaw::deserialize(deserializer)?;
575 let time = match (raw.time, raw.nsec) {
576 (Some(sec), Some(nsec)) => Some(DateTime::<Utc>::from_timestamp(
577 sec.trunc() as i64,
578 ((sec.fract()) * 1e9) as u32 + nsec as u32,
579 )),
580 (Some(sec), None) => Some(DateTime::<Utc>::from_timestamp(
581 sec.trunc() as i64,
582 ((sec.fract()) * 1e9) as u32,
583 )),
584 _ => None,
585 }
586 .flatten();
587
588 Ok(Raw {
589 device: raw.device,
590 time,
591 rawdata: raw.rawdata,
592 })
593 }
594}
595
596/// GPSD response message types
597///
598/// This enum represents all possible response messages from GPSD.
599/// Each variant corresponds to a specific "class" value in the JSON response.
600/// - [libgps_json_unpack](https://gitlab.com/gpsd/gpsd/-/blob/master/libgps/libgps_json.c#L792)
601#[allow(clippy::large_enum_variant)]
602#[derive(Debug, Clone, PartialEq, Deserialize)]
603#[serde(tag = "class", rename_all = "UPPERCASE")]
604pub enum Message {
605 /// Time-Position-Velocity report
606 Tpv(Tpv),
607 /// GPS pseudorange error statistics
608 Gst(Gst),
609 /// Satellite sky view report
610 Sky(Sky),
611 /// Attitude/orientation data
612 Att(Attitude),
613 /// Inertial measurement unit data
614 Imu(Imu),
615 /// List of available GPS devices
616 Devices(DeviceList),
617 /// Single GPS device information
618 Device(Device),
619 /// Current watch settings
620 Watch(Watch),
621 /// GPSD version information
622 Version(Version),
623 /// RTCM2 differential correction data
624 Rtcm2(Rtcm2),
625 /// RTCM3 differential correction data
626 Rtcm3(Rtcm3),
627 // AIS vessel data (commented out)
628 // Ais(Aivdm),
629 /// Error message from GPSD
630 Error(Error),
631 /// Time offset report
632 Toff(TimeOffset),
633 /// Pulse-per-second timing report
634 Pps(Pps),
635 /// Oscillator/clock discipline status
636 Osc(Oscillator),
637 /// Raw GPS receiver data
638 Raw(Raw),
639 /// Poll response with current fixes
640 Poll(Poll),
641 /// Unknown/unsupported message type
642 #[serde(untagged)]
643 Other(String),
644}
645
646/// Helper function to deserialize floating-point Unix timestamps to DateTime
647///
648/// Converts a floating-point number representing seconds since Unix epoch
649/// to a DateTime<Utc> object, preserving sub-second precision.
650fn f64_to_datetime<'de, D>(deserializer: D) -> Result<Option<DateTime<Utc>>, D::Error>
651where
652 D: serde::Deserializer<'de>,
653{
654 let opt = Option::<f64>::deserialize(deserializer)?;
655 Ok(opt.and_then(|float| {
656 DateTime::<Utc>::from_timestamp(float.trunc() as i64, ((float.fract()) * 1e9) as u32)
657 }))
658}
659
660/// Helper function to convert separate seconds and nanoseconds to DateTime
661///
662/// Combines Unix timestamp seconds and nanoseconds into a DateTime<Utc> object.
663fn deserialize_to_datetime(sec: Option<i64>, nsec: Option<i64>) -> Option<DateTime<Utc>> {
664 match (sec, nsec) {
665 (Some(sec), Some(nsec)) => DateTime::<Utc>::from_timestamp(sec, nsec as u32),
666 _ => None,
667 }
668}