tracing_subscriber/fmt/time/datetime.rs
1// musl as a whole is licensed under the following standard MIT license:
2//
3// ----------------------------------------------------------------------
4// Copyright © 2005-2020 Rich Felker, et al.
5//
6// Permission is hereby granted, free of charge, to any person obtaining
7// a copy of this software and associated documentation files (the
8// "Software"), to deal in the Software without restriction, including
9// without limitation the rights to use, copy, modify, merge, publish,
10// distribute, sublicense, and/or sell copies of the Software, and to
11// permit persons to whom the Software is furnished to do so, subject to
12// the following conditions:
13//
14// The above copyright notice and this permission notice shall be
15// included in all copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24// ----------------------------------------------------------------------
25//
26// Authors/contributors include:
27//
28// A. Wilcox
29// Ada Worcester
30// Alex Dowad
31// Alex Suykov
32// Alexander Monakov
33// Andre McCurdy
34// Andrew Kelley
35// Anthony G. Basile
36// Aric Belsito
37// Arvid Picciani
38// Bartosz Brachaczek
39// Benjamin Peterson
40// Bobby Bingham
41// Boris Brezillon
42// Brent Cook
43// Chris Spiegel
44// Clément Vasseur
45// Daniel Micay
46// Daniel Sabogal
47// Daurnimator
48// David Carlier
49// David Edelsohn
50// Denys Vlasenko
51// Dmitry Ivanov
52// Dmitry V. Levin
53// Drew DeVault
54// Emil Renner Berthing
55// Fangrui Song
56// Felix Fietkau
57// Felix Janda
58// Gianluca Anzolin
59// Hauke Mehrtens
60// He X
61// Hiltjo Posthuma
62// Isaac Dunham
63// Jaydeep Patil
64// Jens Gustedt
65// Jeremy Huntwork
66// Jo-Philipp Wich
67// Joakim Sindholt
68// John Spencer
69// Julien Ramseier
70// Justin Cormack
71// Kaarle Ritvanen
72// Khem Raj
73// Kylie McClain
74// Leah Neukirchen
75// Luca Barbato
76// Luka Perkov
77// M Farkas-Dyck (Strake)
78// Mahesh Bodapati
79// Markus Wichmann
80// Masanori Ogino
81// Michael Clark
82// Michael Forney
83// Mikhail Kremnyov
84// Natanael Copa
85// Nicholas J. Kain
86// orc
87// Pascal Cuoq
88// Patrick Oppenlander
89// Petr Hosek
90// Petr Skocik
91// Pierre Carrier
92// Reini Urban
93// Rich Felker
94// Richard Pennington
95// Ryan Fairfax
96// Samuel Holland
97// Segev Finer
98// Shiz
99// sin
100// Solar Designer
101// Stefan Kristiansson
102// Stefan O'Rear
103// Szabolcs Nagy
104// Timo Teräs
105// Trutz Behn
106// Valentin Ochs
107// Will Dietz
108// William Haddon
109// William Pitcock
110//
111// Portions of this software are derived from third-party works licensed
112// under terms compatible with the above MIT license:
113//
114// The TRE regular expression implementation (src/regex/reg* and
115// src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed
116// under a 2-clause BSD license (license text in the source files). The
117// included version has been heavily modified by Rich Felker in 2012, in
118// the interests of size, simplicity, and namespace cleanliness.
119//
120// Much of the math library code (src/math/* and src/complex/*) is
121// Copyright © 1993,2004 Sun Microsystems or
122// Copyright © 2003-2011 David Schultz or
123// Copyright © 2003-2009 Steven G. Kargl or
124// Copyright © 2003-2009 Bruce D. Evans or
125// Copyright © 2008 Stephen L. Moshier or
126// Copyright © 2017-2018 Arm Limited
127// and labelled as such in comments in the individual source files. All
128// have been licensed under extremely permissive terms.
129//
130// The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008
131// The Android Open Source Project and is licensed under a two-clause BSD
132// license. It was taken from Bionic libc, used on Android.
133//
134// The AArch64 memcpy and memset code (src/string/aarch64/*) are
135// Copyright © 1999-2019, Arm Limited.
136//
137// The implementation of DES for crypt (src/crypt/crypt_des.c) is
138// Copyright © 1994 David Burren. It is licensed under a BSD license.
139//
140// The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was
141// originally written by Solar Designer and placed into the public
142// domain. The code also comes with a fallback permissive license for use
143// in jurisdictions that may not recognize the public domain.
144//
145// The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011
146// Valentin Ochs and is licensed under an MIT-style license.
147//
148// The x86_64 port was written by Nicholas J. Kain and is licensed under
149// the standard MIT terms.
150//
151// The mips and microblaze ports were originally written by Richard
152// Pennington for use in the ellcc project. The original code was adapted
153// by Rich Felker for build system and code conventions during upstream
154// integration. It is licensed under the standard MIT terms.
155//
156// The mips64 port was contributed by Imagination Technologies and is
157// licensed under the standard MIT terms.
158//
159// The powerpc port was also originally written by Richard Pennington,
160// and later supplemented and integrated by John Spencer. It is licensed
161// under the standard MIT terms.
162//
163// All other files which have no copyright comments are original works
164// produced specifically for use as part of this library, written either
165// by Rich Felker, the main author of the library, or by one or more
166// contibutors listed above. Details on authorship of individual files
167// can be found in the git version control history of the project. The
168// omission of copyright and license comments in each file is in the
169// interest of source tree size.
170//
171// In addition, permission is hereby granted for all public header files
172// (include/* and arch/*/bits/*) and crt files intended to be linked into
173// applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit
174// the copyright notice and permission notice otherwise required by the
175// license, and to use these files without any requirement of
176// attribution. These files include substantial contributions from:
177//
178// Bobby Bingham
179// John Spencer
180// Nicholas J. Kain
181// Rich Felker
182// Richard Pennington
183// Stefan Kristiansson
184// Szabolcs Nagy
185//
186// all of whom have explicitly granted such permission.
187//
188// This file previously contained text expressing a belief that most of
189// the files covered by the above exception were sufficiently trivial not
190// to be subject to copyright, resulting in confusion over whether it
191// negated the permissions granted in the license. In the spirit of
192// permissive licensing, and of not having licensing issues being an
193// obstacle to adoption, that text has been removed.
194
195use std::fmt;
196
197/// A date/time type which exists primarily to convert `SystemTime` timestamps into an ISO 8601
198/// formatted string.
199///
200/// Yes, this exists. Before you have a heart attack, understand that the meat of this is musl's
201/// [`__secs_to_tm`][1] converted to Rust via [c2rust][2] and then cleaned up by hand as part of
202/// the [kudu-rs project][3], [released under MIT][4].
203///
204/// [1] http://git.musl-libc.org/cgit/musl/tree/src/time/__secs_to_tm.c
205/// [2] https://c2rust.com/
206/// [3] https://github.com/danburkert/kudu-rs/blob/c9660067e5f4c1a54143f169b5eeb49446f82e54/src/timestamp.rs#L5-L18
207/// [4] https://github.com/tokio-rs/tracing/issues/1644#issuecomment-963888244
208///
209/// All existing `strftime`-like APIs I found were unable to handle the full range of timestamps representable
210/// by `SystemTime`, including `strftime` itself, since tm.tm_year is an int.
211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
212pub(crate) struct DateTime {
213 year: i64,
214 month: u8,
215 day: u8,
216 hour: u8,
217 minute: u8,
218 second: u8,
219 nanos: u32,
220}
221
222impl fmt::Display for DateTime {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 if self.year > 9999 {
225 write!(f, "+{}", self.year)?;
226 } else if self.year < 0 {
227 write!(f, "{:05}", self.year)?;
228 } else {
229 write!(f, "{:04}", self.year)?;
230 }
231
232 write!(
233 f,
234 "-{:02}-{:02}T{:02}:{:02}:{:02}.{:06}Z",
235 self.month,
236 self.day,
237 self.hour,
238 self.minute,
239 self.second,
240 self.nanos / 1_000
241 )
242 }
243}
244
245impl From<std::time::SystemTime> for DateTime {
246 fn from(timestamp: std::time::SystemTime) -> DateTime {
247 let (t, nanos) = match timestamp.duration_since(std::time::UNIX_EPOCH) {
248 Ok(duration) => {
249 debug_assert!(duration.as_secs() <= i64::MAX as u64);
250 (duration.as_secs() as i64, duration.subsec_nanos())
251 }
252 Err(error) => {
253 let duration = error.duration();
254 debug_assert!(duration.as_secs() <= i64::MAX as u64);
255 let (secs, nanos) = (duration.as_secs() as i64, duration.subsec_nanos());
256 if nanos == 0 {
257 (-secs, 0)
258 } else {
259 (-secs - 1, 1_000_000_000 - nanos)
260 }
261 }
262 };
263
264 // 2000-03-01 (mod 400 year, immediately after feb29
265 const LEAPOCH: i64 = 946_684_800 + 86400 * (31 + 29);
266 const DAYS_PER_400Y: i32 = 365 * 400 + 97;
267 const DAYS_PER_100Y: i32 = 365 * 100 + 24;
268 const DAYS_PER_4Y: i32 = 365 * 4 + 1;
269 static DAYS_IN_MONTH: [i8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
270
271 // Note(dcb): this bit is rearranged slightly to avoid integer overflow.
272 let mut days: i64 = (t / 86_400) - (LEAPOCH / 86_400);
273 let mut remsecs: i32 = (t % 86_400) as i32;
274 if remsecs < 0i32 {
275 remsecs += 86_400;
276 days -= 1
277 }
278
279 let mut qc_cycles: i32 = (days / i64::from(DAYS_PER_400Y)) as i32;
280 let mut remdays: i32 = (days % i64::from(DAYS_PER_400Y)) as i32;
281 if remdays < 0 {
282 remdays += DAYS_PER_400Y;
283 qc_cycles -= 1;
284 }
285
286 let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
287 if c_cycles == 4 {
288 c_cycles -= 1;
289 }
290 remdays -= c_cycles * DAYS_PER_100Y;
291
292 let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
293 if q_cycles == 25 {
294 q_cycles -= 1;
295 }
296 remdays -= q_cycles * DAYS_PER_4Y;
297
298 let mut remyears: i32 = remdays / 365;
299 if remyears == 4 {
300 remyears -= 1;
301 }
302 remdays -= remyears * 365;
303
304 let mut years: i64 = i64::from(remyears)
305 + 4 * i64::from(q_cycles)
306 + 100 * i64::from(c_cycles)
307 + 400 * i64::from(qc_cycles);
308
309 let mut months: i32 = 0;
310 while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
311 remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
312 months += 1
313 }
314
315 if months >= 10 {
316 months -= 12;
317 years += 1;
318 }
319
320 DateTime {
321 year: years + 2000,
322 month: (months + 3) as u8,
323 day: (remdays + 1) as u8,
324 hour: (remsecs / 3600) as u8,
325 minute: (remsecs / 60 % 60) as u8,
326 second: (remsecs % 60) as u8,
327 nanos,
328 }
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use i32;
335 use std::time::{Duration, UNIX_EPOCH};
336
337 use super::*;
338
339 #[test]
340 fn test_datetime() {
341 let case = |expected: &str, secs: i64, micros: u32| {
342 let timestamp = if secs >= 0 {
343 UNIX_EPOCH + Duration::new(secs as u64, micros * 1_000)
344 } else {
345 (UNIX_EPOCH - Duration::new(!secs as u64 + 1, 0)) + Duration::new(0, micros * 1_000)
346 };
347 assert_eq!(
348 expected,
349 format!("{}", DateTime::from(timestamp)),
350 "secs: {}, micros: {}",
351 secs,
352 micros
353 )
354 };
355
356 // Mostly generated with:
357 // - date -jur <secs> +"%Y-%m-%dT%H:%M:%S.000000Z"
358 // - http://unixtimestamp.50x.eu/
359
360 case("1970-01-01T00:00:00.000000Z", 0, 0);
361
362 case("1970-01-01T00:00:00.000001Z", 0, 1);
363 case("1970-01-01T00:00:00.500000Z", 0, 500_000);
364 case("1970-01-01T00:00:01.000001Z", 1, 1);
365 case("1970-01-01T00:01:01.000001Z", 60 + 1, 1);
366 case("1970-01-01T01:01:01.000001Z", 60 * 60 + 60 + 1, 1);
367 case(
368 "1970-01-02T01:01:01.000001Z",
369 24 * 60 * 60 + 60 * 60 + 60 + 1,
370 1,
371 );
372
373 case("1969-12-31T23:59:59.000000Z", -1, 0);
374 case("1969-12-31T23:59:59.000001Z", -1, 1);
375 case("1969-12-31T23:59:59.500000Z", -1, 500_000);
376 case("1969-12-31T23:58:59.000001Z", -60 - 1, 1);
377 case("1969-12-31T22:58:59.000001Z", -60 * 60 - 60 - 1, 1);
378 case(
379 "1969-12-30T22:58:59.000001Z",
380 -24 * 60 * 60 - 60 * 60 - 60 - 1,
381 1,
382 );
383
384 case("2038-01-19T03:14:07.000000Z", i32::MAX as i64, 0);
385 case("2038-01-19T03:14:08.000000Z", i32::MAX as i64 + 1, 0);
386 case("2038-01-19T03:14:07.000000Z", i32::MAX as i64, 0);
387 case("2038-01-19T03:14:08.000000Z", i32::MAX as i64 + 1, 0);
388 case("1901-12-13T20:45:52.000000Z", i32::MIN as i64, 0);
389 case("1901-12-13T20:45:51.000000Z", i32::MIN as i64 - 1, 0);
390
391 // Skipping these tests on windows as std::time::SystemTime range is low
392 // on Windows compared with that of Unix which can cause the following
393 // high date value tests to panic
394 #[cfg(not(target_os = "windows"))]
395 {
396 case("+292277026596-12-04T15:30:07.000000Z", i64::MAX, 0);
397 case("+292277026596-12-04T15:30:06.000000Z", i64::MAX - 1, 0);
398 case("+292277026596-12-04T15:30:07.000000Z", i64::MAX, 0);
399 case("+292277026596-12-04T15:30:06.000000Z", i64::MAX - 1, 0);
400 case("-292277022657-01-27T08:29:53.000000Z", i64::MIN + 1, 0);
401 }
402
403 case("1900-01-01T00:00:00.000000Z", -2208988800, 0);
404 case("1899-12-31T23:59:59.000000Z", -2208988801, 0);
405 case("0000-01-01T00:00:00.000000Z", -62167219200, 0);
406 case("-0001-12-31T23:59:59.000000Z", -62167219201, 0);
407
408 case("1234-05-06T07:08:09.000000Z", -23215049511, 0);
409 case("-1234-05-06T07:08:09.000000Z", -101097651111, 0);
410 case("2345-06-07T08:09:01.000000Z", 11847456541, 0);
411 case("-2345-06-07T08:09:01.000000Z", -136154620259, 0);
412 }
413}