proto/finger: parse timezones
Build and test / build (push) Successful in 1m34s
Build and test / check (push) Successful in 1m40s
Build and test / test (push) Successful in 1m52s
Build and test / docs (push) Successful in 3m49s

This commit is contained in:
2026-04-29 08:27:31 +09:00
parent ebad14aa02
commit 16b2bc5c27
3 changed files with 70 additions and 21 deletions
Generated
+35
View File
@@ -187,6 +187,16 @@ dependencies = [
"windows-link",
]
[[package]]
name = "chrono-tz"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3"
dependencies = [
"chrono",
"phf",
]
[[package]]
name = "clap"
version = "4.6.1"
@@ -747,6 +757,24 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "phf"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -831,6 +859,7 @@ dependencies = [
"bytes",
"caps",
"chrono",
"chrono-tz",
"clap",
"clap_complete",
"futures-util",
@@ -974,6 +1003,12 @@ dependencies = [
"libc",
]
[[package]]
name = "siphasher"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slab"
version = "0.4.12"
+1
View File
@@ -38,6 +38,7 @@ tokio-util = "0.7.18"
caps = "0.5.6"
users = { version = "0.11.0", default-features = false }
tracing-journald = "0.3.2"
chrono-tz = "0.10.4"
[features]
default = ["systemd"]
+34 -21
View File
@@ -1,6 +1,9 @@
use std::path::PathBuf;
use std::{path::PathBuf, str::FromStr};
use chrono::{DateTime, Datelike, Duration, NaiveDate, NaiveTime, TimeDelta, Utc, Weekday};
use anyhow::Context;
use chrono::{
DateTime, Datelike, Duration, NaiveDate, NaiveTime, TimeDelta, TimeZone, Timelike, Utc, Weekday,
};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
@@ -132,9 +135,13 @@ fn parse_bsd_finger_time(time: &str) -> anyhow::Result<DateTime<Utc>> {
let time_parts: Vec<_> = time.split_ascii_whitespace().collect();
let time = &time_parts[..time_parts.len() - 1].join(" ");
let _timezone = time_parts[time_parts.len() - 1];
let timezone = time_parts[time_parts.len() - 1]
.trim_start_matches('(')
.trim_end_matches(')');
let tz = chrono_tz::Tz::from_str(timezone)
.context(format!("Failed to parse timezone in login time: {}", time))?;
let now = Utc::now();
let mut parts = time.split_whitespace();
let weekday = match parts.next() {
@@ -181,12 +188,13 @@ fn parse_bsd_finger_time(time: &str) -> anyhow::Result<DateTime<Utc>> {
)
})?;
let now = Utc::now();
const MAX_YEARS_BACK: i32 = 10;
let mut year = None;
for offset in 0..=MAX_YEARS_BACK {
let year = now.year() - offset;
let year_ = now.year() - offset;
let date = match NaiveDate::from_ymd_opt(year, month, day) {
let date = match NaiveDate::from_ymd_opt(year_, month, day) {
Some(d) => d,
None => continue,
};
@@ -195,22 +203,26 @@ fn parse_bsd_finger_time(time: &str) -> anyhow::Result<DateTime<Utc>> {
continue;
}
let dt = date.and_time(clock);
if dt <= now.naive_utc() {
// TODO: apply timezone if we are able to parse it.
// if not, try to get the local timezone offset.
// if not, assume UTC.
return Ok(DateTime::<Utc>::from_naive_utc_and_offset(dt, Utc));
}
year = Some(year_);
}
Err(anyhow::anyhow!(
"Could not infer year for login time {} within {} years",
time,
MAX_YEARS_BACK
))
if year.is_none() {
return Err(anyhow::anyhow!(
"Could not find a valid year for login time {} within {} years",
time,
MAX_YEARS_BACK
));
}
tz.with_ymd_and_hms(year.unwrap(), month, day, clock.hour(), clock.minute(), 0)
.single()
.ok_or_else(|| {
anyhow::anyhow!(
"Failed to convert login time to timezone-aware datetime: {}",
time
)
})
.map(|dt| dt.with_timezone(&Utc))
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -763,6 +775,7 @@ mod tests {
"Tue Feb 28 23:59 (UTC)",
"Wed Dec 31 00:00 (UTC)",
"Wed Dec 31 00:00 (GMT)",
"Wed Dec 31 00:00 (Asia/Tokyo)",
];
for input in cases {