Skip to content

Commit 56b841d

Browse files
authored
subscriber: format SystemTime when chrono is disabled (#807)
## Motivation Fixes #791 ## Solution This PR copies `DateTime` type from https://github.com/danburkert/kudu-rs and updates the default `FormatTime` impl for `SystemTime` when chrono is disabled to use `DateTime` formatting instead of `Debug` After this, the following output is emitted instead of the one shown in the issue. ``` 2020-07-11T16:49:15.155481Z INFO example_no_crono: preparing to shave yaks number_of_yaks=3 2020-07-11T16:49:15.155523Z INFO example_no_crono::yak_shave: shaving yaks 2020-07-11T16:49:15.155580Z DEBUG shave{yak=1}: example_no_crono::yak_shave: hello! I'm gonna shave a yak. excitement="yay!" 2020-07-11T16:49:15.155607Z DEBUG shave{yak=1}: example_no_crono::yak_shave: yak shaved successfully 2020-07-11T16:49:15.155665Z DEBUG example_no_crono::yak_shave: yak=1 shaved=true 2020-07-11T16:49:15.155691Z DEBUG example_no_crono::yak_shave: yaks_shaved=1 2020-07-11T16:49:15.155714Z DEBUG shave{yak=2}: example_no_crono::yak_shave: hello! I'm gonna shave a yak. excitement="yay!" 2020-07-11T16:49:15.155765Z DEBUG shave{yak=2}: example_no_crono::yak_shave: yak shaved successfully 2020-07-11T16:49:15.155788Z DEBUG example_no_crono::yak_shave: yak=2 shaved=true 2020-07-11T16:49:15.155799Z DEBUG example_no_crono::yak_shave: yaks_shaved=2 2020-07-11T16:49:15.155820Z DEBUG shave{yak=3}: example_no_crono::yak_shave: hello! I'm gonna shave a yak. excitement="yay!" 2020-07-11T16:49:15.155870Z WARN shave{yak=3}: example_no_crono::yak_shave: could not locate yak! 2020-07-11T16:49:15.155909Z DEBUG example_no_crono::yak_shave: yak=3 shaved=false 2020-07-11T16:49:15.155921Z ERROR example_no_crono::yak_shave: failed to shave yak! yak=3 error=shaving yak failed! 2020-07-11T16:49:15.155932Z DEBUG example_no_crono::yak_shave: yaks_shaved=2 2020-07-11T16:49:15.155941Z INFO example_no_crono: yak shaving completed. all_yaks_shaved=false ``` Signed-off-by: Tarun Pothulapati <[email protected]>
1 parent 028dfdf commit 56b841d

File tree

2 files changed

+223
-2
lines changed

2 files changed

+223
-2
lines changed
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use std::fmt;
2+
3+
/// A date/time type which exists primarily to convert `SystemTime` timestamps into an ISO 8601
4+
/// formatted string.
5+
///
6+
/// Yes, this exists. Before you have a heart attack, understand that the meat of this is musl's
7+
/// [`__secs_to_tm`][1] converted to Rust via [c2rust][2] and then cleaned up by hand. All existing
8+
/// `strftime`-like APIs I found were unable to handle the full range of timestamps representable
9+
/// by `SystemTime`, including `strftime` itself, since tm.tm_year is an int.
10+
///
11+
/// TODO: figure out how to properly attribute the MIT licensed musl project.
12+
///
13+
/// [1] http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c
14+
/// [2] https://c2rust.com/
15+
///
16+
/// This is directly copy-pasted from https://github.com/danburkert/kudu-rs/blob/c9660067e5f4c1a54143f169b5eeb49446f82e54/src/timestamp.rs#L5-L18
17+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18+
pub(crate) struct DateTime {
19+
year: i64,
20+
month: u8,
21+
day: u8,
22+
hour: u8,
23+
minute: u8,
24+
second: u8,
25+
nanos: u32,
26+
}
27+
28+
impl fmt::Display for DateTime {
29+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30+
if self.year > 9999 {
31+
write!(f, "+{}", self.year)?;
32+
} else if self.year < 0 {
33+
write!(f, "{:05}", self.year)?;
34+
} else {
35+
write!(f, "{:04}", self.year)?;
36+
}
37+
38+
write!(
39+
f,
40+
"-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
41+
self.month,
42+
self.day,
43+
self.hour,
44+
self.minute,
45+
self.second,
46+
self.nanos / 1_000
47+
)
48+
}
49+
}
50+
51+
impl From<std::time::SystemTime> for DateTime {
52+
fn from(timestamp: std::time::SystemTime) -> DateTime {
53+
let (t, nanos) = match timestamp.duration_since(std::time::UNIX_EPOCH) {
54+
Ok(duration) => {
55+
debug_assert!(duration.as_secs() <= i64::MAX as u64);
56+
(duration.as_secs() as i64, duration.subsec_nanos())
57+
}
58+
Err(error) => {
59+
let duration = error.duration();
60+
debug_assert!(duration.as_secs() <= i64::MAX as u64);
61+
let (secs, nanos) = (duration.as_secs() as i64, duration.subsec_nanos());
62+
if nanos == 0 {
63+
(-secs, 0)
64+
} else {
65+
(-secs - 1, 1_000_000_000 - nanos)
66+
}
67+
}
68+
};
69+
70+
// 2000-03-01 (mod 400 year, immediately after feb29
71+
const LEAPOCH: i64 = 946_684_800 + 86400 * (31 + 29);
72+
const DAYS_PER_400Y: i32 = 365 * 400 + 97;
73+
const DAYS_PER_100Y: i32 = 365 * 100 + 24;
74+
const DAYS_PER_4Y: i32 = 365 * 4 + 1;
75+
static DAYS_IN_MONTH: [i8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
76+
77+
// Note(dcb): this bit is rearranged slightly to avoid integer overflow.
78+
let mut days: i64 = (t / 86_400) - (LEAPOCH / 86_400);
79+
let mut remsecs: i32 = (t % 86_400) as i32;
80+
if remsecs < 0i32 {
81+
remsecs += 86_400;
82+
days -= 1
83+
}
84+
85+
let mut qc_cycles: i32 = (days / i64::from(DAYS_PER_400Y)) as i32;
86+
let mut remdays: i32 = (days % i64::from(DAYS_PER_400Y)) as i32;
87+
if remdays < 0 {
88+
remdays += DAYS_PER_400Y;
89+
qc_cycles -= 1;
90+
}
91+
92+
let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
93+
if c_cycles == 4 {
94+
c_cycles -= 1;
95+
}
96+
remdays -= c_cycles * DAYS_PER_100Y;
97+
98+
let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
99+
if q_cycles == 25 {
100+
q_cycles -= 1;
101+
}
102+
remdays -= q_cycles * DAYS_PER_4Y;
103+
104+
let mut remyears: i32 = remdays / 365;
105+
if remyears == 4 {
106+
remyears -= 1;
107+
}
108+
remdays -= remyears * 365;
109+
110+
let mut years: i64 = i64::from(remyears)
111+
+ 4 * i64::from(q_cycles)
112+
+ 100 * i64::from(c_cycles)
113+
+ 400 * i64::from(qc_cycles);
114+
115+
let mut months: i32 = 0;
116+
while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
117+
remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
118+
months += 1
119+
}
120+
121+
if months >= 10 {
122+
months -= 12;
123+
years += 1;
124+
}
125+
126+
DateTime {
127+
year: years + 2000,
128+
month: (months + 3) as u8,
129+
day: (remdays + 1) as u8,
130+
hour: (remsecs / 3600) as u8,
131+
minute: (remsecs / 60 % 60) as u8,
132+
second: (remsecs % 60) as u8,
133+
nanos,
134+
}
135+
}
136+
}
137+
138+
#[cfg(test)]
139+
mod tests {
140+
use std::i32;
141+
use std::time::{Duration, UNIX_EPOCH};
142+
143+
use super::*;
144+
145+
#[test]
146+
fn test_datetime() {
147+
let case = |expected: &str, secs: i64, micros: u32| {
148+
let timestamp = if secs >= 0 {
149+
UNIX_EPOCH + Duration::new(secs as u64, micros * 1_000)
150+
} else {
151+
(UNIX_EPOCH - Duration::new(!secs as u64 + 1, 0)) + Duration::new(0, micros * 1_000)
152+
};
153+
assert_eq!(
154+
expected,
155+
format!("{}", DateTime::from(timestamp)),
156+
"secs: {}, micros: {}",
157+
secs,
158+
micros
159+
)
160+
};
161+
162+
// Mostly generated with:
163+
// - date -jur <secs> +"%Y-%m-%dT%H:%M:%S.000000Z"
164+
// - http://unixtimestamp.50x.eu/
165+
166+
case("1970-01-01T00:00:00.000000Z", 0, 0);
167+
168+
case("1970-01-01T00:00:00.000001Z", 0, 1);
169+
case("1970-01-01T00:00:00.500000Z", 0, 500_000);
170+
case("1970-01-01T00:00:01.000001Z", 1, 1);
171+
case("1970-01-01T00:01:01.000001Z", 60 + 1, 1);
172+
case("1970-01-01T01:01:01.000001Z", 60 * 60 + 60 + 1, 1);
173+
case(
174+
"1970-01-02T01:01:01.000001Z",
175+
24 * 60 * 60 + 60 * 60 + 60 + 1,
176+
1,
177+
);
178+
179+
case("1969-12-31T23:59:59.000000Z", -1, 0);
180+
case("1969-12-31T23:59:59.000001Z", -1, 1);
181+
case("1969-12-31T23:59:59.500000Z", -1, 500_000);
182+
case("1969-12-31T23:58:59.000001Z", -60 - 1, 1);
183+
case("1969-12-31T22:58:59.000001Z", -60 * 60 - 60 - 1, 1);
184+
case(
185+
"1969-12-30T22:58:59.000001Z",
186+
-24 * 60 * 60 - 60 * 60 - 60 - 1,
187+
1,
188+
);
189+
190+
case("2038-01-19T03:14:07.000000Z", i32::MAX as i64, 0);
191+
case("2038-01-19T03:14:08.000000Z", i32::MAX as i64 + 1, 0);
192+
case("1901-12-13T20:45:52.000000Z", i32::MIN as i64, 0);
193+
case("1901-12-13T20:45:51.000000Z", i32::MIN as i64 - 1, 0);
194+
195+
// Skipping these tests on windows as std::time::SysteTime range is low
196+
// on Windows compared with that of Unix which can cause the following
197+
// high date value tests to panic
198+
#[cfg(not(target_os = "windows"))]
199+
{
200+
case("+292277026596-12-04T15:30:07.000000Z", i64::MAX, 0);
201+
case("+292277026596-12-04T15:30:06.000000Z", i64::MAX - 1, 0);
202+
case("-292277022657-01-27T08:29:53.000000Z", i64::MIN + 1, 0);
203+
}
204+
205+
case("1900-01-01T00:00:00.000000Z", -2208988800, 0);
206+
case("1899-12-31T23:59:59.000000Z", -2208988801, 0);
207+
case("0000-01-01T00:00:00.000000Z", -62167219200, 0);
208+
case("-0001-12-31T23:59:59.000000Z", -62167219201, 0);
209+
210+
case("1234-05-06T07:08:09.000000Z", -23215049511, 0);
211+
case("-1234-05-06T07:08:09.000000Z", -101097651111, 0);
212+
case("2345-06-07T08:09:01.000000Z", 11847456541, 0);
213+
case("-2345-06-07T08:09:01.000000Z", -136154620259, 0);
214+
}
215+
}

tracing-subscriber/src/fmt/time.rs renamed to tracing-subscriber/src/fmt/time/mod.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
//! Formatters for event timestamps.
22
#[cfg(feature = "ansi")]
33
use ansi_term::Style;
4-
54
use std::fmt;
65
use std::time::Instant;
76

7+
#[cfg(not(feature = "chrono"))]
8+
mod datetime;
9+
810
/// A type that can measure and format the current time.
911
///
1012
/// This trait is used by `Format` to include a timestamp with each `Event` when it is logged.
@@ -119,7 +121,11 @@ impl FormatTime for SystemTime {
119121
#[cfg(not(feature = "chrono"))]
120122
impl FormatTime for SystemTime {
121123
fn format_time(&self, w: &mut dyn fmt::Write) -> fmt::Result {
122-
write!(w, "{:?}", std::time::SystemTime::now())
124+
write!(
125+
w,
126+
"{}",
127+
datetime::DateTime::from(std::time::SystemTime::now())
128+
)
123129
}
124130
}
125131

0 commit comments

Comments
 (0)