Skip to content

Commit

Permalink
new: added support for floating point timestamps
Browse files Browse the repository at this point in the history
  • Loading branch information
pamburus committed Mar 19, 2024
1 parent 69752e3 commit e256824
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ categories = ["command-line-utilities"]
description = "Utility for viewing json-formatted log files."
keywords = ["cli", "human", "log"]
name = "hl"
version = "0.27.0-beta.2.1"
version = "0.27.0-beta.3"
edition = "2021"
build = "build.rs"

Expand Down
79 changes: 67 additions & 12 deletions src/timestamp.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// third-party imports
use chrono::{DateTime, FixedOffset, NaiveDateTime};
use chrono::{DateTime, Duration, FixedOffset, NaiveDateTime};

// ---

Expand All @@ -20,18 +20,38 @@ impl<'a> Timestamp<'a> {
return parsed;
}

if let Ok(ts) = self.0.parse::<i64>() {
let (ts, nsec) = if ts < 100000000000 {
(ts, 0)
} else if ts < 100000000000000 {
(ts / 1000, (ts % 1000) * 1000000)
} else {
(ts / 1000000, (ts % 1000000) * 1000)
};
let ts = NaiveDateTime::from_timestamp_opt(ts, nsec as u32)?;
Some(DateTime::from_naive_utc_and_offset(ts, FixedOffset::east_opt(0)?))
} else if let Ok(ts) = self.0.parse() {
if let Ok(ts) = self.0.parse() {
Some(ts)
} else if let Some(nt) = guess_number_type(self.0.as_bytes()) {
let ts = match nt {
NumberType::Integer => self.0.parse::<i64>().ok().and_then(|ts| match ts {
Self::TS_UNIX_AUTO_S_MIN..=Self::TS_UNIX_AUTO_S_MAX => NaiveDateTime::from_timestamp_opt(ts, 0),
Self::TS_UNIX_AUTO_MS_MIN..=Self::TS_UNIX_AUTO_MS_MAX => NaiveDateTime::from_timestamp_millis(ts),
Self::TS_UNIX_AUTO_US_MIN..=Self::TS_UNIX_AUTO_US_MAX => NaiveDateTime::from_timestamp_micros(ts),
_ => NaiveDateTime::from_timestamp_nanos(ts),
}),
NumberType::Float => self.0.bytes().position(|b| b == b'.').and_then(|i| {
let whole = self.0[..i].parse::<i64>().ok()?;
let fractional = self.0[i..].parse::<f64>().ok()?;
match whole {
Self::TS_UNIX_AUTO_S_MIN..=Self::TS_UNIX_AUTO_S_MAX => {
let ns = (fractional * 1e9).round() as u32;
let ns = if whole < 0 { 1_000_000_000 - ns } else { ns };
NaiveDateTime::from_timestamp_opt(whole, ns)
}
Self::TS_UNIX_AUTO_MS_MIN..=Self::TS_UNIX_AUTO_MS_MAX => {
let ns = (fractional * 1e6).round() as i64;
NaiveDateTime::from_timestamp_millis(whole).map(|ts| ts + Duration::nanoseconds(ns))
}
Self::TS_UNIX_AUTO_US_MIN..=Self::TS_UNIX_AUTO_US_MAX => {
let ns = (fractional * 1e3).round() as i64;
NaiveDateTime::from_timestamp_micros(whole).map(|ts| ts + Duration::nanoseconds(ns))
}
_ => NaiveDateTime::from_timestamp_nanos(whole),
}
}),
};
ts.and_then(|ts| Some(DateTime::from_naive_utc_and_offset(ts, FixedOffset::east_opt(0)?)))
} else {
NaiveDateTime::parse_from_str(self.0, "%Y-%m-%d %H:%M:%S%.f")
.ok()
Expand All @@ -47,6 +67,13 @@ impl<'a> Timestamp<'a> {
self.parse()
.and_then(|ts| Some((ts.timestamp(), ts.timestamp_subsec_nanos())))
}

const TS_UNIX_AUTO_S_MIN: i64 = -62135596800;
const TS_UNIX_AUTO_S_MAX: i64 = 253402300799;
const TS_UNIX_AUTO_MS_MIN: i64 = Self::TS_UNIX_AUTO_S_MIN * 1000;
const TS_UNIX_AUTO_MS_MAX: i64 = Self::TS_UNIX_AUTO_S_MAX * 1000;
const TS_UNIX_AUTO_US_MIN: i64 = Self::TS_UNIX_AUTO_MS_MIN * 1000;
const TS_UNIX_AUTO_US_MAX: i64 = Self::TS_UNIX_AUTO_MS_MAX * 1000;
}

// ---
Expand Down Expand Up @@ -397,6 +424,34 @@ fn only_digits(b: &[u8]) -> bool {
b.iter().map(|&b| b.is_ascii_digit()).position(|x| x == false).is_none()
}

fn guess_number_type(b: &[u8]) -> Option<NumberType> {
if b.len() == 0 {
return None;
}

let b = if b[0] == b'-' || b[0] == b'+' { &b[1..] } else { b };
let mut dots = 0;
let mut check = |b| match b {
b'.' => {
dots += 1;
dots <= 1
}
b'0'..=b'9' => true,
_ => return false,
};

match (b.iter().map(|b| check(*b)).position(|x| x == false).is_none(), dots) {
(true, 0) => Some(NumberType::Integer),
(true, 1) => Some(NumberType::Float),
_ => None,
}
}

enum NumberType {
Integer,
Float,
}

// ---

#[cfg(test)]
Expand Down

0 comments on commit e256824

Please sign in to comment.