//@ignore-target: windows # no libc time APIs on Windows //@compile-flags: -Zmiri-disable-isolation use std::time::{Duration, Instant}; use std::{env, mem, ptr}; fn main() { test_clocks(); test_posix_gettimeofday(); test_localtime_r_gmt(); test_localtime_r_pst(); test_localtime_r_epoch(); #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] test_localtime_r_multiple_calls_deduplication(); // Architecture-specific tests. #[cfg(target_pointer_width = "32")] test_localtime_r_future_32b(); #[cfg(target_pointer_width = "64")] test_localtime_r_future_64b(); test_nanosleep(); #[cfg(any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "solaris", target_os = "illumos" ))] { test_clock_nanosleep::absolute(); test_clock_nanosleep::relative(); } } /// Tests whether clock support exists at all fn test_clocks() { let mut tp = mem::MaybeUninit::::uninit(); let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, tp.as_mut_ptr()) }; assert_eq!(is_error, 0); let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, tp.as_mut_ptr()) }; assert_eq!(is_error, 0); #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "android"))] { let is_error = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME_COARSE, tp.as_mut_ptr()) }; assert_eq!(is_error, 0); let is_error = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_COARSE, tp.as_mut_ptr()) }; assert_eq!(is_error, 0); } #[cfg(target_os = "macos")] { let is_error = unsafe { libc::clock_gettime(libc::CLOCK_UPTIME_RAW, tp.as_mut_ptr()) }; assert_eq!(is_error, 0); } } fn test_posix_gettimeofday() { let mut tp = mem::MaybeUninit::::uninit(); let tz = ptr::null_mut::(); let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz.cast()) }; assert_eq!(is_error, 0); let tv = unsafe { tp.assume_init() }; assert!(tv.tv_sec > 0); assert!(tv.tv_usec >= 0); // Theoretically this could be 0. // Test that non-null tz returns an error. let mut tz = mem::MaybeUninit::::uninit(); let tz_ptr = tz.as_mut_ptr(); let is_error = unsafe { libc::gettimeofday(tp.as_mut_ptr(), tz_ptr.cast()) }; assert_eq!(is_error, -1); } /// Helper function to create an empty tm struct. fn create_empty_tm() -> libc::tm { libc::tm { tm_sec: 0, tm_min: 0, tm_hour: 0, tm_mday: 0, tm_mon: 0, tm_year: 0, tm_wday: 0, tm_yday: 0, tm_isdst: 0, #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] tm_gmtoff: 0, #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] tm_zone: std::ptr::null_mut::(), } } /// Original GMT test fn test_localtime_r_gmt() { // Set timezone to GMT. let key = "TZ"; env::set_var(key, "GMT"); const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) }; assert_eq!(tm.tm_sec, 56); assert_eq!(tm.tm_min, 43); assert_eq!(tm.tm_hour, 7); assert_eq!(tm.tm_mday, 7); assert_eq!(tm.tm_mon, 3); assert_eq!(tm.tm_year, 124); assert_eq!(tm.tm_wday, 0); assert_eq!(tm.tm_yday, 97); assert_eq!(tm.tm_isdst, -1); #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] { assert_eq!(tm.tm_gmtoff, 0); unsafe { assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); } } // The returned value is the pointer passed in. assert!(ptr::eq(res, &mut tm)); // Remove timezone setting. env::remove_var(key); } /// PST timezone test (testing different timezone handling). fn test_localtime_r_pst() { let key = "TZ"; env::set_var(key, "PST8PDT"); const TIME_SINCE_EPOCH: libc::time_t = 1712475836; // 2024-04-07 07:43:56 GMT let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) }; assert_eq!(tm.tm_sec, 56); assert_eq!(tm.tm_min, 43); assert_eq!(tm.tm_hour, 0); // 7 - 7 = 0 (PDT offset) assert_eq!(tm.tm_mday, 7); assert_eq!(tm.tm_mon, 3); assert_eq!(tm.tm_year, 124); assert_eq!(tm.tm_wday, 0); assert_eq!(tm.tm_yday, 97); assert_eq!(tm.tm_isdst, -1); // DST information unavailable #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] { assert_eq!(tm.tm_gmtoff, -7 * 3600); // -7 hours in seconds unsafe { assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "-07"); } } assert!(ptr::eq(res, &mut tm)); env::remove_var(key); } /// Unix epoch test (edge case testing). fn test_localtime_r_epoch() { let key = "TZ"; env::set_var(key, "GMT"); const TIME_SINCE_EPOCH: libc::time_t = 0; // 1970-01-01 00:00:00 let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) }; assert_eq!(tm.tm_sec, 0); assert_eq!(tm.tm_min, 0); assert_eq!(tm.tm_hour, 0); assert_eq!(tm.tm_mday, 1); assert_eq!(tm.tm_mon, 0); assert_eq!(tm.tm_year, 70); assert_eq!(tm.tm_wday, 4); // Thursday assert_eq!(tm.tm_yday, 0); assert_eq!(tm.tm_isdst, -1); #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] { assert_eq!(tm.tm_gmtoff, 0); unsafe { assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); } } assert!(ptr::eq(res, &mut tm)); env::remove_var(key); } /// Future date test (testing large values). #[cfg(target_pointer_width = "64")] fn test_localtime_r_future_64b() { let key = "TZ"; env::set_var(key, "GMT"); // Using 2050-01-01 00:00:00 for 64-bit systems // value that's safe for 64-bit time_t const TIME_SINCE_EPOCH: libc::time_t = 2524608000; let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) }; assert_eq!(tm.tm_sec, 0); assert_eq!(tm.tm_min, 0); assert_eq!(tm.tm_hour, 0); assert_eq!(tm.tm_mday, 1); assert_eq!(tm.tm_mon, 0); assert_eq!(tm.tm_year, 150); // 2050 - 1900 assert_eq!(tm.tm_wday, 6); // Saturday assert_eq!(tm.tm_yday, 0); assert_eq!(tm.tm_isdst, -1); #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] { assert_eq!(tm.tm_gmtoff, 0); unsafe { assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); } } assert!(ptr::eq(res, &mut tm)); env::remove_var(key); } /// Future date test (testing large values for 32b target). #[cfg(target_pointer_width = "32")] fn test_localtime_r_future_32b() { let key = "TZ"; env::set_var(key, "GMT"); // Using 2030-01-01 00:00:00 for 32-bit systems // Safe value within i32 range const TIME_SINCE_EPOCH: libc::time_t = 1893456000; let custom_time_ptr = &TIME_SINCE_EPOCH; let mut tm = create_empty_tm(); let res = unsafe { libc::localtime_r(custom_time_ptr, &mut tm) }; // Verify 2030-01-01 00:00:00 assert_eq!(tm.tm_sec, 0); assert_eq!(tm.tm_min, 0); assert_eq!(tm.tm_hour, 0); assert_eq!(tm.tm_mday, 1); assert_eq!(tm.tm_mon, 0); assert_eq!(tm.tm_year, 130); // 2030 - 1900 assert_eq!(tm.tm_wday, 2); // Tuesday assert_eq!(tm.tm_yday, 0); assert_eq!(tm.tm_isdst, -1); #[cfg(any( target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android" ))] { assert_eq!(tm.tm_gmtoff, 0); unsafe { assert_eq!(std::ffi::CStr::from_ptr(tm.tm_zone).to_str().unwrap(), "+00"); } } assert!(ptr::eq(res, &mut tm)); env::remove_var(key); } /// Tests the behavior of `localtime_r` with multiple calls to ensure deduplication of `tm_zone` pointers. #[cfg(any(target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "android"))] fn test_localtime_r_multiple_calls_deduplication() { let key = "TZ"; env::set_var(key, "PST8PDT"); const TIME_SINCE_EPOCH_BASE: libc::time_t = 1712475836; // Base timestamp: 2024-04-07 07:43:56 GMT const NUM_CALLS: usize = 50; let mut unique_pointers = std::collections::HashSet::new(); for i in 0..NUM_CALLS { let timestamp = TIME_SINCE_EPOCH_BASE + (i as libc::time_t * 3600); // Increment by 1 hour for each call let mut tm: libc::tm = create_empty_tm(); let tm_ptr = unsafe { libc::localtime_r(×tamp, &mut tm) }; assert!(!tm_ptr.is_null(), "localtime_r failed for timestamp {timestamp}"); unique_pointers.insert(tm.tm_zone); } let unique_count = unique_pointers.len(); assert!( unique_count >= 2 && unique_count <= (NUM_CALLS - 1), "Unexpected number of unique tm_zone pointers: {} (expected between 2 and {})", unique_count, NUM_CALLS - 1 ); } fn test_nanosleep() { let start_test_sleep = Instant::now(); let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; let remainder = ptr::null_mut::(); let is_error = unsafe { libc::nanosleep(&duration_zero, remainder) }; assert_eq!(is_error, 0); assert!(start_test_sleep.elapsed() < Duration::from_millis(100)); let start_test_sleep = Instant::now(); let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; let remainder = ptr::null_mut::(); let is_error = unsafe { libc::nanosleep(&duration_100_millis, remainder) }; assert_eq!(is_error, 0); assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); } #[cfg(any( target_os = "freebsd", target_os = "linux", target_os = "android", target_os = "solaris", target_os = "illumos" ))] mod test_clock_nanosleep { use super::*; /// Helper function used to create an instant in the future fn add_100_millis(mut ts: libc::timespec) -> libc::timespec { // While tv_nsec has type `c_long` tv_sec has type `time_t`. These might // end up as different types (for example: like i32 and i64). const SECOND: libc::c_long = 1_000_000_000; ts.tv_nsec += SECOND / 10; // If this pushes tv_nsec to SECOND or higher, we need to overflow to tv_sec. ts.tv_sec += (ts.tv_nsec / SECOND) as libc::time_t; ts.tv_nsec %= SECOND; ts } /// Helper function to get the current time for testing relative sleeps fn timespec_now(clock: libc::clockid_t) -> libc::timespec { let mut timespec = mem::MaybeUninit::::uninit(); let is_error = unsafe { libc::clock_gettime(clock, timespec.as_mut_ptr()) }; assert_eq!(is_error, 0); unsafe { timespec.assume_init() } } pub fn absolute() { let start_test_sleep = Instant::now(); let before_start = libc::timespec { tv_sec: 0, tv_nsec: 0 }; let remainder = ptr::null_mut::(); let error = unsafe { // this will not sleep since unix time zero is in the past libc::clock_nanosleep( libc::CLOCK_MONOTONIC, libc::TIMER_ABSTIME, &before_start, remainder, ) }; assert_eq!(error, 0); assert!(start_test_sleep.elapsed() < Duration::from_millis(100)); let start_test_sleep = Instant::now(); let hunderd_millis_after_start = add_100_millis(timespec_now(libc::CLOCK_MONOTONIC)); let remainder = ptr::null_mut::(); let error = unsafe { libc::clock_nanosleep( libc::CLOCK_MONOTONIC, libc::TIMER_ABSTIME, &hunderd_millis_after_start, remainder, ) }; assert_eq!(error, 0); assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); } pub fn relative() { const NO_FLAGS: i32 = 0; let start_test_sleep = Instant::now(); let duration_zero = libc::timespec { tv_sec: 0, tv_nsec: 0 }; let remainder = ptr::null_mut::(); let error = unsafe { libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_zero, remainder) }; assert_eq!(error, 0); assert!(start_test_sleep.elapsed() < Duration::from_millis(100)); let start_test_sleep = Instant::now(); let duration_100_millis = libc::timespec { tv_sec: 0, tv_nsec: 1_000_000_000 / 10 }; let remainder = ptr::null_mut::(); let error = unsafe { libc::clock_nanosleep(libc::CLOCK_MONOTONIC, NO_FLAGS, &duration_100_millis, remainder) }; assert_eq!(error, 0); assert!(start_test_sleep.elapsed() > Duration::from_millis(100)); } }