Merge branch 'more-time' into 'master'

Few improvements to `time.h`

See merge request redox-os/relibc!622
This commit is contained in:
Jeremy Soller
2025-02-26 23:58:57 +00:00
8 changed files with 142 additions and 121 deletions
+92 -119
View File
@@ -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 <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/time.h.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/time.h.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/difftime.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getdate.html>.
// #[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/gmtime.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/localtime.html>.
@@ -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 <https://www.man7.org/linux/man-pages/man3/timegm.3.html>.
#[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 <https://www.man7.org/linux/man-pages/man3/timegm.3.html>.
#[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 <https://pubs.opengroup.org/onlinepubs/9799919799/functions/timer_create.html>.
@@ -627,6 +558,29 @@ pub unsafe extern "C" fn tzset() {
set_timezone(&mut lock, &std_time, dst_time)
}
fn convert_tm_generic<Tz: TimeZone>(tz: &Tz, tm_val: &tm) -> Option<DateTime<Tz>> {
// 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<CString>, Option<CString>)>) {
guard.0 = None;
guard.1 = None;
@@ -638,6 +592,7 @@ fn clear_timezone(guard: &mut MutexGuard<'_, (Option<CString>, Option<CString>)>
}
}
#[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<DateTime<Tz>>, Option<DateTime<Tz>>) {
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<Tz>) -> 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 _;
+2 -1
View File
@@ -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 \
+2 -1
View File
@@ -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 \
+46
View File
@@ -0,0 +1,46 @@
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
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;
}