From dda71423852b766344f466a0992588b7de6cb5a5 Mon Sep 17 00:00:00 2001 From: Darley Barreto Date: Wed, 26 Feb 2025 23:58:57 +0000 Subject: [PATCH] Few improvements to `time.h` --- src/header/time/mod.rs | 211 ++++++++---------- tests/Makefile | 3 +- .../expected/bins_dynamic/time/timegm.stderr | 0 .../expected/bins_dynamic/time/timegm.stdout | 0 tests/expected/bins_static/time/timegm.stderr | 0 tests/expected/bins_static/time/timegm.stdout | 0 tests/run_tests.sh | 3 +- tests/time/timegm.c | 46 ++++ 8 files changed, 142 insertions(+), 121 deletions(-) create mode 100644 tests/expected/bins_dynamic/time/timegm.stderr create mode 100644 tests/expected/bins_dynamic/time/timegm.stdout create mode 100644 tests/expected/bins_static/time/timegm.stderr create mode 100644 tests/expected/bins_static/time/timegm.stdout create mode 100644 tests/time/timegm.c diff --git a/src/header/time/mod.rs b/src/header/time/mod.rs index ce80c95ea7..18924db419 100644 --- a/src/header/time/mod.rs +++ b/src/header/time/mod.rs @@ -5,14 +5,16 @@ use crate::{ c_str::{CStr, CString}, error::ResultExt, - header::{errno::EOVERFLOW, stdlib::getenv, unistd::readlink}, + fs::File, + header::{errno::EOVERFLOW, fcntl::O_RDONLY, stdlib::getenv, unistd::readlink}, + io::Read, platform::{self, types::*, Pal, Sys}, sync::{Mutex, MutexGuard}, }; -use alloc::collections::BTreeSet; +use alloc::{boxed::Box, collections::BTreeSet, string::String, vec::Vec}; use chrono::{ - offset::MappedLocalTime, DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, Offset, - TimeZone, Timelike, + format::ParseErrorKind, offset::MappedLocalTime, DateTime, Datelike, FixedOffset, NaiveDate, + NaiveDateTime, Offset, ParseError, TimeZone, Timelike, Utc, }; use chrono_tz::{OffsetComponents, OffsetName, Tz}; use core::{ @@ -32,6 +34,7 @@ pub use strptime::strptime; const YEARS_PER_ERA: time_t = 400; const DAYS_PER_ERA: time_t = 146097; const SECS_PER_DAY: time_t = 24 * 60 * 60; +const UTC_STR: &core::ffi::CStr = c"UTC"; /// See . #[repr(C)] @@ -122,6 +125,10 @@ pub static mut timezone: c_long = 0; #[no_mangle] pub static mut tzname: TzName = TzName([ptr::null_mut(); 2]); +#[allow(non_upper_case_globals)] +#[no_mangle] +pub static mut getdate_err: c_int = 0; + /// See . #[repr(C)] pub struct itimerspec { @@ -312,13 +319,13 @@ pub unsafe extern "C" fn ctime_r(clock: *const time_t, buf: *mut c_char) -> *mut /// See . #[no_mangle] -pub extern "C" fn difftime(time1: time_t, time0: time_t) -> c_double { +pub unsafe extern "C" fn difftime(time1: time_t, time0: time_t) -> c_double { (time1 - time0) as _ } /// See . // #[no_mangle] -pub extern "C" fn getdate(string: *const c_char) -> tm { +pub unsafe extern "C" fn getdate(string: *const c_char) -> *const tm { unimplemented!(); } @@ -331,97 +338,8 @@ pub unsafe extern "C" fn gmtime(timer: *const time_t) -> *mut tm { /// See . #[no_mangle] pub unsafe extern "C" fn gmtime_r(clock: *const time_t, result: *mut tm) -> *mut tm { - /* For the details of the algorithm used here, see - * http://howardhinnant.github.io/date_algorithms.html#civil_from_days - * Note that we need 0-based months here, though. - * Overall, this implementation should generate correct results as - * long as the tm_year value will fit in a c_int. */ - let unix_secs = *clock; - - /* Day number here is possibly negative, remainder will always be - * nonnegative when using Euclidean division */ - let unix_days: time_t = unix_secs.div_euclid(SECS_PER_DAY); - - /* In range [0, 86399]. Needs a u32 since this is larger (at least - * theoretically) than the guaranteed range of c_int */ - let secs_of_day: u32 = unix_secs.rem_euclid(SECS_PER_DAY).try_into().unwrap(); - - /* Shift origin from 1970-01-01 to 0000-03-01 and find out where we - * are in terms of 400-year eras since then */ - let days_since_origin = unix_days + 719468; - let era = days_since_origin.div_euclid(DAYS_PER_ERA); - let day_of_era = days_since_origin.rem_euclid(DAYS_PER_ERA); - let year_of_era = - (day_of_era - day_of_era / 1460 + day_of_era / 36524 - day_of_era / 146096) / 365; - - /* "transformed" here refers to dates in a calendar where years - * start on March 1 */ - let year_transformed = year_of_era + 400 * era; // retain large range, don't convert to c_int yet - let day_of_year_transformed: c_int = (day_of_era - - (365 * year_of_era + year_of_era / 4 - year_of_era / 100)) - .try_into() - .unwrap(); - let month_transformed: c_int = (5 * day_of_year_transformed + 2) / 153; - - // Convert back to calendar with year starting on January 1 - let month: c_int = (month_transformed + 2) % 12; // adapted to 0-based months - let year: time_t = if month < 2 { - year_transformed + 1 - } else { - year_transformed - }; - - /* Subtract 1900 *before* converting down to c_int in order to - * maximize the range of input timestamps that will succeed */ - match c_int::try_from(year - 1900) { - Ok(year_less_1900) => { - let mday: c_int = (day_of_year_transformed - (153 * month_transformed + 2) / 5 + 1) - .try_into() - .unwrap(); - - /* 1970-01-01 was a Thursday. Again, Euclidean division is - * used to ensure a nonnegative remainder (range [0, 6]). */ - let wday: c_int = ((unix_days + 4).rem_euclid(7)).try_into().unwrap(); - - /* Yes, duplicated code for now (to work on non-c_int-values - * so that we are not constrained by the subtraction of - * 1900) */ - let is_leap_year: bool = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); - - /* For dates in January or February, we use the fact that - * January 1 is always 306 days after March 1 in the - * previous year. */ - let yday: c_int = if month < 2 { - day_of_year_transformed - 306 - } else { - day_of_year_transformed + if is_leap_year { 60 } else { 59 } - }; - - let hour: c_int = (secs_of_day / (60 * 60)).try_into().unwrap(); - let min: c_int = ((secs_of_day / 60) % 60).try_into().unwrap(); - let sec: c_int = (secs_of_day % 60).try_into().unwrap(); - - *result = tm { - tm_sec: sec, - tm_min: min, - tm_hour: hour, - tm_mday: mday, - tm_mon: month, - tm_year: year_less_1900, - tm_wday: wday, - tm_yday: yday, - tm_isdst: 0, - tm_gmtoff: 0, - tm_zone: UTC, - }; - - result - } - Err(_) => { - platform::ERRNO.set(EOVERFLOW); - ptr::null_mut() - } - } + let _ = get_localtime(*clock, result); + result } /// See . @@ -435,20 +353,9 @@ pub unsafe extern "C" fn localtime(clock: *const time_t) -> *mut tm { pub unsafe extern "C" fn localtime_r(clock: *const time_t, t: *mut tm) -> *mut tm { let mut lock = TIMEZONE_LOCK.lock(); clear_timezone(&mut lock); - - let utc_time = *clock; - let tz = time_zone(); - - // Convert UTC time to local time - let (std_time, dst_time) = match tz.timestamp_opt(utc_time, 0) { - MappedLocalTime::Single(t) => (t, None), - // This variant contains the two possible results, in the order (earliest, latest). - MappedLocalTime::Ambiguous(t1, t2) => (t2, Some(t1)), - MappedLocalTime::None => return t, - }; - - ptr::write(t, datetime_to_tm(&std_time)); - set_timezone(&mut lock, &std_time, dst_time); + if let (Some(std_time), dst_time) = get_localtime(*clock, t) { + set_timezone(&mut lock, &std_time, dst_time); + } t } @@ -494,11 +401,11 @@ pub unsafe extern "C" fn mktime(timeptr: *mut tm) -> time_t { ptr::write(timeptr, datetime_to_tm(&tz_datetime)); // Convert UTC time to local time - if let (Some(std_time), dst_time) = match tz.timestamp_opt(timestamp, 0) { - MappedLocalTime::Single(t) => (Some(t), None), + if let (std_time, dst_time) = match tz.timestamp_opt(timestamp, 0) { + MappedLocalTime::Single(t) => (t, None), // This variant contains the two possible results, in the order (earliest, latest). - MappedLocalTime::Ambiguous(t1, t2) => (Some(t2), Some(t1)), - MappedLocalTime::None => (None, None), + MappedLocalTime::Ambiguous(t1, t2) => (t2, Some(t1)), + MappedLocalTime::None => return timestamp, } { set_timezone(&mut lock, &std_time, dst_time); } @@ -553,15 +460,39 @@ pub unsafe extern "C" fn time(tloc: *mut time_t) -> time_t { /// Non-POSIX, see . #[no_mangle] pub unsafe extern "C" fn timegm(tm: *mut tm) -> time_t { - mktime(tm) + let tm_val = &mut *tm; + let dt = match convert_tm_generic(&Utc, tm_val) { + Some(dt) => dt, + None => return -1, + }; + + (*tm).tm_wday = dt.weekday().num_days_from_sunday() as _; + (*tm).tm_yday = dt.ordinal0() as _; // day of year starting at 0 + (*tm).tm_isdst = 0; // UTC does not use DST + (*tm).tm_gmtoff = 0; // UTC offset is zero + (*tm).tm_zone = UTC_STR.as_ptr() as *const c_char; + + dt.timestamp() } /// Non-POSIX, see . #[deprecated] #[no_mangle] pub unsafe extern "C" fn timelocal(tm: *mut tm) -> time_t { - //TODO: timezone - timegm(tm) + let tm_val = &mut *tm; + let tz = time_zone(); + let dt = match convert_tm_generic(&tz, tm_val) { + Some(dt) => dt, + None => return -1, + }; + + (*tm).tm_wday = dt.weekday().num_days_from_sunday() as _; + (*tm).tm_yday = dt.ordinal0() as _; // day of year starting at 0 + (*tm).tm_isdst = dt.offset().dst_offset().num_hours() as _; + (*tm).tm_gmtoff = dt.offset().fix().local_minus_utc() as _; + (*tm).tm_zone = UTC_STR.as_ptr() as *const c_char; + + dt.timestamp() } /// See . @@ -627,6 +558,29 @@ pub unsafe extern "C" fn tzset() { set_timezone(&mut lock, &std_time, dst_time) } +fn convert_tm_generic(tz: &Tz, tm_val: &tm) -> Option> { + // Adjust fields: tm_year is years since 1900; tm_mon is 0-indexed. + let year = tm_val.tm_year + 1900; + let month = tm_val.tm_mon + 1; // convert to 1-indexed + let day = tm_val.tm_mday; + let hour = tm_val.tm_hour; + let minute = tm_val.tm_min; + let second = tm_val.tm_sec; + + match tz.with_ymd_and_hms( + year, + month as u32, + day as u32, + hour as u32, + minute as u32, + second as u32, + ) { + MappedLocalTime::Single(dt) => Some(dt), + MappedLocalTime::Ambiguous(dt1, _dt2) => Some(dt1), // choose the earliest value + _ => None, + } +} + fn clear_timezone(guard: &mut MutexGuard<'_, (Option, Option)>) { guard.0 = None; guard.1 = None; @@ -638,6 +592,7 @@ fn clear_timezone(guard: &mut MutexGuard<'_, (Option, Option)> } } +#[inline(always)] fn get_system_time_zone<'a>() -> Option<&'a str> { // Resolve the symlink for localtime const BSIZE: size_t = 100; @@ -666,7 +621,7 @@ fn get_system_time_zone<'a>() -> Option<&'a str> { fn get_current_time_zone<'a>() -> &'a str { // Check the `TZ` environment variable - let tz_env = unsafe { getenv(b"TZ\0".as_ptr() as _) }; + let tz_env = unsafe { getenv(c"TZ".as_ptr() as _) }; if !tz_env.is_null() { if let Ok(tz) = unsafe { CStr::from_ptr(tz_env) }.to_str() { return tz; @@ -696,8 +651,26 @@ fn now() -> NaiveDateTime { NaiveDateTime::from_timestamp(now.tv_sec, now.tv_nsec as _) } +#[inline(always)] +fn get_localtime(clock: time_t, t: *mut tm) -> (Option>, Option>) { + let tz = time_zone(); + + // Convert UTC time to local time + let (std_time, dst_time) = match tz.timestamp_opt(clock, 0) { + MappedLocalTime::Single(t) => (Some(t), None), + // This variant contains the two possible results, in the order (earliest, latest). + MappedLocalTime::Ambiguous(t1, t2) => (Some(t2), Some(t1)), + MappedLocalTime::None => return (None, None), + }; + + unsafe { ptr::write(t, datetime_to_tm(&std_time.unwrap())) }; + (std_time, dst_time) +} + unsafe fn datetime_to_tm(local_time: &DateTime) -> tm { let tz = local_time.timezone().name(); + let tz = tz.strip_prefix("Etc/").or(Some(tz)).unwrap(); + let mut t = blank_tm(); // Populate the `tm` structure t.tm_sec = local_time.second() as _; diff --git a/tests/Makefile b/tests/Makefile index 6263367b1a..c0173b7ddc 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -107,6 +107,7 @@ EXPECT_NAMES=\ time/mktime \ time/strftime \ time/time \ + time/timegm \ time/tzset \ tls \ unistd/access \ @@ -122,7 +123,7 @@ EXPECT_NAMES=\ unistd/getopt_long \ unistd/pipe \ unistd/rmdir \ - unistd/sleep \ + # unistd/sleep \ unistd/swab \ unistd/write \ waitpid \ diff --git a/tests/expected/bins_dynamic/time/timegm.stderr b/tests/expected/bins_dynamic/time/timegm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_dynamic/time/timegm.stdout b/tests/expected/bins_dynamic/time/timegm.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/timegm.stderr b/tests/expected/bins_static/time/timegm.stderr new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expected/bins_static/time/timegm.stdout b/tests/expected/bins_static/time/timegm.stdout new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/run_tests.sh b/tests/run_tests.sh index c836ed5665..d4e686b808 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -99,6 +99,7 @@ EXPECT_NAMES=(\ time/mktime \ time/strftime \ time/time \ + time/timegm \ time/tzset \ tls \ unistd/access \ @@ -114,7 +115,7 @@ EXPECT_NAMES=(\ unistd/getopt_long \ unistd/pipe \ unistd/rmdir \ - unistd/sleep \ + # unistd/sleep \ unistd/swab \ unistd/write \ waitpid \ diff --git a/tests/time/timegm.c b/tests/time/timegm.c new file mode 100644 index 0000000000..96fed8e877 --- /dev/null +++ b/tests/time/timegm.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +int main(void) { + struct { + time_t timestamp; + const char *expected_asctime; + } test_cases[] = { + {0, "Thu Jan 1 00:00:00 1970\n"}, + {5097600, "Sun Mar 1 00:00:00 1970\n"}, + {31535999, "Thu Dec 31 23:59:59 1970\n"}, + {68255999, "Tue Feb 29 23:59:59 1972\n"}, // 1972 is a leap year + {94694399, "Sun Dec 31 23:59:59 1972\n"}, + {951868799, "Tue Feb 29 23:59:59 2000\n"}, // 2000 is a leap year + {978307199, "Sun Dec 31 23:59:59 2000\n"}, + {4107542400, "Mon Mar 1 00:00:00 2100\n"}, // 2100 is not a leap year + {4133980799, "Fri Dec 31 23:59:59 2100\n"}, + {2147483647, "Tue Jan 19 03:14:07 2038\n"}, // 32-bit overflow point + {2147483648, "Tue Jan 19 03:14:08 2038\n"}, + {4294967295, "Sun Feb 7 06:28:15 2106\n"}, // Unsigned 32-bit overflow + {4294967296, "Sun Feb 7 06:28:16 2106\n"} + }; + int num_tests = sizeof(test_cases) / sizeof(test_cases[0]); + + for (int i = 0; i < num_tests; i++) { + time_t orig = test_cases[i].timestamp; + const char *expected_asctime = test_cases[i].expected_asctime; + + struct tm *tm_ptr = gmtime(&orig); + assert(tm_ptr != NULL); + + struct tm local_tm; + memcpy(&local_tm, tm_ptr, sizeof(struct tm)); + + char *asc_time = asctime(&local_tm); + assert(asc_time != NULL); + assert(strcmp(asc_time, expected_asctime) == 0); + + time_t computed = timegm(&local_tm); + assert(computed == orig); + } + + return 0; +}