From 07d13de6a585e4da90c493d78d1a09d32491b971 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Mon, 20 Jul 2026 19:08:45 +0900 Subject: [PATCH] Provide times as unix seconds for all `--json` outputs --- src/lib.rs | 1 + src/proto/finger_protocol.rs | 15 ++++++++++---- src/proto/rwhod_protocol.rs | 14 ++++++++++--- src/util.rs | 1 + src/util/duration_serde.rs | 38 ++++++++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 src/util.rs create mode 100644 src/util/duration_serde.rs diff --git a/src/lib.rs b/src/lib.rs index ee5d18b..dae6a9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,4 @@ pub mod proto; pub mod server; +pub mod util; diff --git a/src/proto/finger_protocol.rs b/src/proto/finger_protocol.rs index ef4668d..540da02 100644 --- a/src/proto/finger_protocol.rs +++ b/src/proto/finger_protocol.rs @@ -6,9 +6,12 @@ use std::path::PathBuf; use chrono::{DateTime, TimeDelta, Utc}; use serde::{Deserialize, Serialize}; -use crate::proto::finger_protocol::{ - classic_formatter::classic_format_finger_response_structured_user_entry, - parser::try_parse_structured_user_entry_from_raw_finger_response, +use crate::{ + proto::finger_protocol::{ + classic_formatter::classic_format_finger_response_structured_user_entry, + parser::try_parse_structured_user_entry_from_raw_finger_response, + }, + util::duration_serde, }; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -243,10 +246,12 @@ impl FingerResponseStructuredUserEntry { pub enum MailStatus { NoMail, NewMailReceived { + #[serde(with = "chrono::serde::ts_seconds")] received_time: DateTime, + #[serde(with = "chrono::serde::ts_seconds")] unread_since: DateTime, }, - MailLastRead(DateTime), + MailLastRead(#[serde(with = "chrono::serde::ts_seconds")] DateTime), } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -255,12 +260,14 @@ pub struct FingerResponseUserSession { pub tty: String, /// When the user logged in and created this session + #[serde(with = "chrono::serde::ts_seconds")] pub login_time: DateTime, /// The hostname or address of the machine from which the user is logged in, if available pub host: Option, /// The amount of time since the use last interacted with the tty + #[serde(with = "duration_serde::duration_seconds_option")] pub idle_time: Option, /// Whether this tty is writable, and thus can receive messages via `mesg(1)` diff --git a/src/proto/rwhod_protocol.rs b/src/proto/rwhod_protocol.rs index 51de36a..957ee93 100644 --- a/src/proto/rwhod_protocol.rs +++ b/src/proto/rwhod_protocol.rs @@ -6,9 +6,12 @@ use bytes::{Buf, BufMut, BytesMut}; use chrono::{DateTime, Duration, Utc}; use serde::{Deserialize, Serialize}; -use crate::proto::rwhod_protocol::time_codec::{ - RwhodTimestamps, decode_rwhod_timestamp_near_time, decode_rwhod_timestamps, - encode_rwhod_timestamp, +use crate::{ + proto::rwhod_protocol::time_codec::{ + RwhodTimestamps, decode_rwhod_timestamp_near_time, decode_rwhod_timestamps, + encode_rwhod_timestamp, + }, + util::duration_serde, }; /// Classic C struct for utmp data for a single user session. @@ -256,9 +259,11 @@ pub type LoadAverage = (i32, i32, i32); pub struct WhodStatusUpdate { // NOTE: there is only one defined packet type, so we just omit it here /// Timestamp by sender + #[serde(with = "chrono::serde::ts_seconds")] pub sendtime: DateTime, /// Timestamp applied by receiver + #[serde(with = "chrono::serde::ts_seconds_option")] pub recvtime: Option>, /// Name of the host sending the status update (max 32 characters) @@ -268,6 +273,7 @@ pub struct WhodStatusUpdate { pub load_average: LoadAverage, /// Which time the system was booted + #[serde(with = "chrono::serde::ts_seconds")] pub boot_time: DateTime, /// List of users currently logged in to the host (max 42 entries) @@ -307,9 +313,11 @@ pub struct WhodUserEntry { pub user_id: String, /// Time when the user logged in + #[serde(with = "chrono::serde::ts_seconds")] pub login_time: DateTime, /// How long since the user last typed on the TTY + #[serde(with = "duration_serde::duration_seconds")] pub idle_time: Duration, } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..f231180 --- /dev/null +++ b/src/util.rs @@ -0,0 +1 @@ +pub mod duration_serde; diff --git a/src/util/duration_serde.rs b/src/util/duration_serde.rs new file mode 100644 index 0000000..569f81c --- /dev/null +++ b/src/util/duration_serde.rs @@ -0,0 +1,38 @@ +pub mod duration_seconds { + use chrono::TimeDelta; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; + + pub fn serialize( + duration: &TimeDelta, + serializer: S, + ) -> Result { + duration.num_seconds().serialize(serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result { + let secs = i64::deserialize(deserializer)?; + TimeDelta::try_seconds(secs).ok_or_else(|| Error::custom("duration out of range")) + } +} + +pub mod duration_seconds_option { + use chrono::TimeDelta; + use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; + + pub fn serialize( + duration: &Option, + serializer: S, + ) -> Result { + duration.map(|d| d.num_seconds()).serialize(serializer) + } + + pub fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + let secs = Option::::deserialize(deserializer)?; + secs.map(|secs| { + TimeDelta::try_seconds(secs).ok_or_else(|| Error::custom("duration out of range")) + }) + .transpose() + } +}