From b29bc9eb8b68052eb469bf39823ce3fee6429b1d Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Sat, 1 Sep 2018 06:02:16 +0100 Subject: [PATCH 1/2] std: rename `time.rs` to `time/mod.rs` Needed to add another mod in `time`, see following commit. --- src/libstd/{time.rs => time/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/libstd/{time.rs => time/mod.rs} (100%) diff --git a/src/libstd/time.rs b/src/libstd/time/mod.rs similarity index 100% rename from src/libstd/time.rs rename to src/libstd/time/mod.rs From ba2d7d8f17ce110ed20fc68c8e922d77b8de1cd7 Mon Sep 17 00:00:00 2001 From: Stepan Koltsov Date: Sat, 1 Sep 2018 20:08:58 +0100 Subject: [PATCH 2/2] Implement Display for SystemTime Time is formatted as ISO-8601 in UTC timezone. Nanoseconds are printed by default, unless precision is specified: ``` 1966-10-31T14:13:20.123456789Z // by default: {} 1966-10-31T14:13:20Z // with format {.0} 1966-10-31T14:13:20.1Z // with format {.1} ``` Implementation is partially copied from `chrono` crate (CC @quodlibetor). This is not fully-featured date-time implementation. The motivation for this commit is to be able to quickly understand what system time is (e. g. when printed in logs) without introducing external dependencies. Format is similar to `java.time.Instant.toString()` output except that Java truncates trailing zeros by default: https://repl.it/repls/NauticalSeveralWheel I think it's better to not truncate zeros by default, because: * data output (e. g. in logs) look better when field length is the same, e. g. with default Java formatter this is how log output may look like: ``` 1966-10-31T14:13:20.123456789Z Server started 1966-10-31T14:13:21Z Connection accepted 1966-10-31T14:13:21.567Z Request started ``` * precision information is lost (e. g. when reading string 1966-10-31T14:13:20Z it's not clear, is it exactly zero millis, or timestamp has seconds precision) This patch depends on leap seconds clarification: https://github.com/rust-lang/rust/pull/53579 --- src/libstd/lib.rs | 1 + src/libstd/time/iso_8601.rs | 328 ++++++++++++++++++++++++++++++++++++ src/libstd/time/mod.rs | 99 +++++++++++ 3 files changed, 428 insertions(+) create mode 100644 src/libstd/time/iso_8601.rs diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index e7195b3e21ee3..c7c7c6086f531 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -295,6 +295,7 @@ #![feature(staged_api)] #![feature(stmt_expr_attributes)] #![feature(str_internals)] +#![feature(system_time_display_iso_8601)] #![feature(rustc_private)] #![feature(thread_local)] #![feature(toowned_clone_into)] diff --git a/src/libstd/time/iso_8601.rs b/src/libstd/time/iso_8601.rs new file mode 100644 index 0000000000000..c85a3a4a51edd --- /dev/null +++ b/src/libstd/time/iso_8601.rs @@ -0,0 +1,328 @@ +use fmt; +use time::Duration; + + +// Number of seconds in a day is a constant. +// We do not support leap seconds here. +const SECONDS_IN_DAY: u64 = 86400; + + +// Gregorian calendar has 400 years cycles, this is a procedure +// for computing if a year is a leap year. +fn is_leap_year(year: i64) -> bool { + if year % 4 != 0 { + false + } else if year % 100 != 0 { + true + } else if year % 400 != 0 { + false + } else { + true + } +} + +fn days_in_year(year: i64) -> u32 { + if is_leap_year(year) { + 366 + } else { + 365 + } +} + + +// Number of leap years among 400 consecutive years. +const CYCLE_LEAP_YEARS: u32 = 400 / 4 - 400 / 100 + 400 / 400; +// Number of days in 400 years cycle. +const CYCLE_DAYS: u32 = 400 * 365 + CYCLE_LEAP_YEARS; +const CYCLE_SECONDS: u64 = CYCLE_DAYS as u64 * SECONDS_IN_DAY; + + +// Number of seconds between 1 Jan 1970 and 1 Jan 2000. +// Check with: +// `TZ=UTC gdate --rfc-3339=seconds --date @946684800` +const YEARS_1970_2000_SECONDS: u64 = 946684800; +// Number of seconds between 1 Jan 1600 and 1 Jan 1970. +const YEARS_1600_1970_SECONDS: u64 = CYCLE_SECONDS - YEARS_1970_2000_SECONDS; + + +// For each year in the cycle, number of leap years before in the cycle. +static YEAR_DELTAS: [u8; 401] = [ + 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, + 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, + 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, + 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, // 100 + 25, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, + 29, 30, 30, 30, 30, 31, 31, 31, 31, 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, + 34, 35, 35, 35, 35, 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, + 39, 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43, 44, 44, 44, + 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 48, 48, 48, 48, 49, 49, 49, // 200 + 49, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51, 52, 52, 52, 52, 53, 53, 53, + 53, 54, 54, 54, 54, 55, 55, 55, 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, + 58, 59, 59, 59, 59, 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, + 63, 64, 64, 64, 64, 65, 65, 65, 65, 66, 66, 66, 66, 67, 67, 67, 67, 68, 68, 68, + 68, 69, 69, 69, 69, 70, 70, 70, 70, 71, 71, 71, 71, 72, 72, 72, 72, 73, 73, 73, // 300 + 73, 73, 73, 73, 73, 74, 74, 74, 74, 75, 75, 75, 75, 76, 76, 76, 76, 77, 77, 77, + 77, 78, 78, 78, 78, 79, 79, 79, 79, 80, 80, 80, 80, 81, 81, 81, 81, 82, 82, 82, + 82, 83, 83, 83, 83, 84, 84, 84, 84, 85, 85, 85, 85, 86, 86, 86, 86, 87, 87, 87, + 87, 88, 88, 88, 88, 89, 89, 89, 89, 90, 90, 90, 90, 91, 91, 91, 91, 92, 92, 92, + 92, 93, 93, 93, 93, 94, 94, 94, 94, 95, 95, 95, 95, 96, 96, 96, 96, 97, 97, 97, 97, +]; + + +/// UTC time +pub struct TmUtc { + /// Year + year: i64, + /// 1..=12 + month: u32, + /// 1-based day of month + day: u32, + /// 0..=23 + hour: u32, + /// 0..=59 + minute: u32, + /// 0..=59; no leap seconds + second: u32, + /// 0..=999_999_999 + nanos: u32, +} + +impl TmUtc { + + fn day_of_cycle_to_year_day_of_year(day_of_cycle: u32) -> (i64, u32) { + debug_assert!(day_of_cycle < CYCLE_DAYS); + + let mut year_mod_400 = (day_of_cycle / 365) as i64; + let mut day_or_year = (day_of_cycle % 365) as u32; + + let delta = YEAR_DELTAS[year_mod_400 as usize] as u32; + if day_or_year < delta { + year_mod_400 -= 1; + day_or_year += 365 - YEAR_DELTAS[year_mod_400 as usize] as u32; + } else { + day_or_year -= delta; + } + + (year_mod_400, day_or_year) + } + + // Convert seconds of the day of hour, minute and second + fn seconds_of_day_to_h_m_s(seconds: u32) -> (u32, u32, u32) { + debug_assert!(seconds < SECONDS_IN_DAY as u32); + + let hour = seconds / 3600; + let minute = seconds % 3600 / 60; + let second = seconds % 60; + + (hour, minute, second) + } + + // Convert day of year (0-based) to month and day + fn day_of_year_to_month_day(year: i64, day_of_year: u32) -> (u32, u32) { + debug_assert!(day_of_year < days_in_year(year)); + + let days_in_months = if is_leap_year(year) { + &[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + } else { + &[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + }; + + let mut rem_days = day_of_year; + let mut month = 1; + while rem_days >= days_in_months[month - 1] { + rem_days -= days_in_months[month - 1]; + month += 1; + } + + debug_assert!(rem_days + 1 <= days_in_months[month - 1]); + + (month as u32, rem_days + 1) + } + + // Construct from duration added to cycle start year + fn from_cycle_start_add_duration(mut cycle_start: i64, add: Duration) -> TmUtc { + debug_assert!(cycle_start % 400 == 0); + + // Split duration to days and duration within day + + let days = add.as_secs() / SECONDS_IN_DAY; + let duration_of_day = add - Duration::from_secs(days * SECONDS_IN_DAY); + + let cycles = days / CYCLE_DAYS as u64; + cycle_start += cycles as i64 * 400; + let day_of_cycle = days % CYCLE_DAYS as u64; + + let (year_mod_400, day_of_year) = + TmUtc::day_of_cycle_to_year_day_of_year(day_of_cycle as u32); + + let (year,) = (cycle_start + year_mod_400,); + let (month, day) = TmUtc::day_of_year_to_month_day(year, day_of_year); + let (hour, minute, second) = + TmUtc::seconds_of_day_to_h_m_s(duration_of_day.as_secs() as u32); + + TmUtc { + year, + month, + day, + hour, + minute, + second, + nanos: duration_of_day.subsec_nanos(), + } + } + + // Construct from duration added to epoch + pub fn from_epoch_add(add: Duration) -> TmUtc { + // Special case to prevent integer overflow in duration addition + if add > Duration::from_secs(YEARS_1970_2000_SECONDS) { + TmUtc::from_cycle_start_add_duration( + 2000, add - Duration::from_secs(YEARS_1970_2000_SECONDS)) + } else { + TmUtc::from_cycle_start_add_duration( + 1600, add + Duration::from_secs(YEARS_1600_1970_SECONDS)) + } + } + + // Construct from duration subtracted from epoch + pub fn from_epoch_sub(mut sub: Duration) -> TmUtc { + + // Make `sub` less than 400 years + + // Number of full leap cycles in `sub` + let leap_cycles = sub.as_secs() / CYCLE_SECONDS; + let year = 1970 - (leap_cycles * 400) as i64; + sub -= Duration::from_secs(leap_cycles * CYCLE_SECONDS); + + // Convert subtraction to addition + // and align year to a cycle start + + // `year` is still 370 years ahead of cycle start. + debug_assert!((year % 400 + 400) % 400 == 370); + + // Subtract 370 years to get to cycle start + // and additional 400 year to prevent integer underflow in duration subtraction + let cycle_start = year - 400 - 370; + let add_from_cycle_start = + Duration::from_secs(CYCLE_SECONDS + YEARS_1600_1970_SECONDS) - sub; + + debug_assert!(cycle_start % 400 == 0); + + return TmUtc::from_cycle_start_add_duration(cycle_start, add_from_cycle_start); + } + + pub fn fmt_iso_8601(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.year > 9999 { + // ISO-8601 extended format requires leading plus + write!(f, "+{}", self.year)?; + } else if self.year < 0 { + // At least 4 digits + write!(f, "{:05}", self.year)?; + } else { + write!(f, "{:04}", self.year)?; + } + + write!(f, "-{:02}-{:02}T{:02}:{:02}:{:02}", + self.month, self.day, self.hour, self.minute, self.second)?; + + // if width is not specified, print nanoseconds + let subsec_digits = f.precision().unwrap_or(9); + if subsec_digits != 0 { + let width = if subsec_digits > 9 { 9 } else { subsec_digits }; + + // "Truncated" nanonseconds. + let mut subsec = self.nanos; + + // Performs 8 iterations when width=1, + // but that's probably not a issue compared to other computations. + for _ in width..9 { + subsec /= 10; + } + + write!(f, ".{:0width$}", subsec, width=width as usize)?; + + // Adding more than 9 digits is meaningless, + // but if user requests it, we should print zeros. + if subsec_digits > 9 { + write!(f, "{:0n$}", 0, n=subsec_digits - 9)?; + } + } + + write!(f, "Z") + } +} + +#[cfg(test)] +mod test { + use super::*; + use time::Duration; + use fmt; + use u64; + + struct TmUtcDisplayIso8601(TmUtc); + + impl fmt::Display for TmUtcDisplayIso8601 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt_iso_8601(f) + } + } + + #[test] + fn test_fmt() { + fn test_impl(expected: &str, secs: i64, nanos: i32, subsec_digits: u32) { + if nanos != 0 { + assert_eq!(secs >= 0, nanos >= 0); + } + + let time = TmUtcDisplayIso8601(if secs >= 0 { + TmUtc::from_epoch_add(Duration::new(secs as u64, nanos as u32)) + } else { + TmUtc::from_epoch_sub(Duration::new(-secs as u64, -nanos as u32)) + }); + + assert_eq!(expected, format!("{:.digits$}", time, digits=subsec_digits as usize)); + } + + // Tests can be validated with with GNU date: + // `TZ=UTC gdate --date @1535585179 --iso-8601=seconds` + + test_impl("1970-01-01T00:00:00Z", 0, 0, 0); + test_impl("2018-08-29T23:26:19Z", 1535585179, 0, 0); + test_impl("2018-08-29T23:26:19.123Z", 1535585179, 123456789, 3); + test_impl("1646-04-01T03:45:44Z", -10216613656, 0, 0); + // Last day of year in regular and leap year + test_impl("2018-12-31T00:00:00Z", 1546214400, 0, 0); + test_impl("2020-12-31T00:00:00Z", 1609372800, 0, 0); + // More than 9 digits in precision + test_impl("1970-01-01T00:00:00.000000001000Z", 0, 1, 12); + // Large years + test_impl("5138-11-16T09:46:40Z", 100000000000, 0, 0); + // Leading zero + test_impl("0000-12-31T00:00:00Z", -62135683200, 0, 0); + // Minus zero + test_impl("-0003-10-30T14:13:20Z", -62235683200, 0, 0); + // More than 4 digits + test_impl("+33658-09-27T01:46:41Z", 1000000000001, 0, 0); + // Largest value GNU date can handle + test_impl("+2147485547-12-31T23:59:59Z", 67768036191676799, 0, 0); + // Negative dates + test_impl("1969-12-31T23:59:59Z", -1, 0, 0); + test_impl("1969-12-31T23:59:00Z", -60, 0, 0); + test_impl("1969-12-31T23:59:58.900Z", -1, -100_000_000, 3); + test_impl("1966-10-31T14:13:20Z", -100000000, 0, 0); + test_impl("-29719-04-05T22:13:19Z", -1000000000001, 0, 0); + // Smallest value GNU date can handle + test_impl("-2147481748-01-01T00:00:00Z", -67768040609740800, 0, 0); + } + + #[test] + fn test_fmt_max_duration() { + let duration = Duration::new(u64::max_value(), 999_999_999); + // Simply check that there are no integer overflows. + // I didn't check that resulting strings are correct. + assert_eq!("+584554051223-11-09T07:00:15.999999999Z", + format!("{}", TmUtcDisplayIso8601(TmUtc::from_epoch_add(duration)))); + assert_eq!("-584554047284-02-23T16:59:44.000000001Z", + format!("{}", TmUtcDisplayIso8601(TmUtc::from_epoch_sub(duration)))); + } +} diff --git a/src/libstd/time/mod.rs b/src/libstd/time/mod.rs index 90ab349159915..85bae077e3121 100644 --- a/src/libstd/time/mod.rs +++ b/src/libstd/time/mod.rs @@ -27,10 +27,13 @@ use fmt; use ops::{Add, Sub, AddAssign, SubAssign}; use sys::time; use sys_common::FromInner; +use self::iso_8601::TmUtc; #[stable(feature = "time", since = "1.3.0")] pub use core::time::Duration; +mod iso_8601; + /// A measurement of a monotonically nondecreasing clock. /// Opaque and useful only with `Duration`. /// @@ -357,6 +360,44 @@ impl SystemTime { pub fn elapsed(&self) -> Result { SystemTime::now().duration_since(*self) } + + // Convert `SystemTime` to UTC date and time + fn to_tm_utc(&self) -> TmUtc { + match self.duration_since(SystemTime::UNIX_EPOCH) { + Ok(add) => TmUtc::from_epoch_add(add), + Err(sub) => TmUtc::from_epoch_sub(sub.duration()), + } + } + + /// Display using ISO-8601 format + /// `YYYY-MM-DD'T'hh:mm:ss.sssssssss'Z'`. + /// + /// Time is always printed in UTC time zone, and with nanoseconds by + /// default. Precision specifies which number of sub-second digits + /// is displayed. + /// + /// Note this is just a default printing implementation, not a + /// full calendar library. For obtaining components like year or + /// month, printing in a different format or in different time zone, + /// external crates can be used. + /// + /// # Examples + /// + /// ``` + /// #![feature(system_time_display_iso_8601)] + /// use std::time::SystemTime; + /// + /// assert_eq!("1970-01-01T00:00:00.000000000Z", + /// format!("{}", SystemTime::UNIX_EPOCH.display_iso_8601())); + /// assert_eq!("1970-01-01T00:00:00.000Z", + /// format!("{:.3}", SystemTime::UNIX_EPOCH.display_iso_8601())); + /// assert_eq!("1970-01-01T00:00:00Z", + /// format!("{:.0}", SystemTime::UNIX_EPOCH.display_iso_8601())); + /// ``` + #[unstable(feature = "system_time_display_iso_8601", issue = "53891")] + pub fn display_iso_8601(&self) -> SystemTimeDisplayIso8601 { + SystemTimeDisplayIso8601(*self) + } } #[stable(feature = "time2", since = "1.8.0")] @@ -398,6 +439,49 @@ impl fmt::Debug for SystemTime { } } +/// Helper struct for printing system time with [`format!`] and `{}` +/// using ISO-8601 format. +/// +/// This `struct` implements the [`Display`] trait. It is created by +/// the [`display_iso_8601`][`SystemTime::display_iso_8601`] method of +/// [`SystemTime`]. +/// +/// # Examples +/// +/// ``` +/// #![feature(system_time_display_iso_8601)] +/// use std::time::SystemTime; +/// +/// assert_eq!("1970-01-01T00:00:00.000000000Z", +/// format!("{}", SystemTime::UNIX_EPOCH.display_iso_8601())); +/// assert_eq!("1970-01-01T00:00:00.000Z", +/// format!("{:.3}", SystemTime::UNIX_EPOCH.display_iso_8601())); +/// assert_eq!("1970-01-01T00:00:00Z", +/// format!("{:.0}", SystemTime::UNIX_EPOCH.display_iso_8601())); +/// ``` +/// +/// [`Display`]: ../../std/fmt/trait.Display.html +/// [`format!`]: ../../std/macro.format.html +/// [`SystemTime`]: struct.SystemTime.html +/// [`SystemTime::display_iso_8601`]: struct.SystemTime.html#method.display_iso_8601 +#[unstable(feature = "system_time_display_iso_8601", issue = "53891")] +pub struct SystemTimeDisplayIso8601(SystemTime); + +#[unstable(feature = "system_time_display_iso_8601", issue = "53891")] +impl fmt::Display for SystemTimeDisplayIso8601 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let tm_utc = self.0.to_tm_utc(); + tm_utc.fmt_iso_8601(f) + } +} + +#[unstable(feature = "system_time_display_iso_8601", issue = "53891")] +impl fmt::Debug for SystemTimeDisplayIso8601 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + /// An anchor in time which can be used to create new `SystemTime` instances or /// learn about where in time a `SystemTime` lies. /// @@ -590,4 +674,19 @@ mod tests { let hundred_twenty_years = thirty_years * 4; assert!(a < hundred_twenty_years); } + + #[test] + fn system_time_display() { + // Just test flags here. + // Proper time conversion tests are next to `TmUtc` implementation. + let t = (SystemTime::UNIX_EPOCH + Duration::from_nanos(2_345_678_901)) + .display_iso_8601(); + assert_eq!("1970-01-01T00:00:02.345678901Z", format!("{}", t)); + assert_eq!("1970-01-01T00:00:02.34567890100Z", format!("{:.11}", t)); + assert_eq!("1970-01-01T00:00:02.345678901Z", format!("{:.9}", t)); + assert_eq!("1970-01-01T00:00:02.345678Z", format!("{:.6}", t)); + assert_eq!("1970-01-01T00:00:02.345Z", format!("{:.3}", t)); + assert_eq!("1970-01-01T00:00:02.3Z", format!("{:.1}", t)); + assert_eq!("1970-01-01T00:00:02Z", format!("{:.0}", t)); + } }