diff --git a/src/time/src/constants.rs b/src/time/src/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..609dc6eb256817a165db53af3ce9ed7cd6d13e12 --- /dev/null +++ b/src/time/src/constants.rs @@ -0,0 +1,39 @@ +#[cfg(target_os = "linux")] +#[path = "linux.rs"] +pub mod sys; + +#[cfg(target_os = "redox")] +#[path = "redox.rs"] +pub mod sys; + +pub use sys::*; +use platform::types::*; + +// Move epoch from 01.01.1970 to 01.03.0000 (yes, Year 0) - this is the first +// day of a 400-year long "era", right after additional day of leap year. +// This adjustment is required only for date calculation, so instead of +// modifying time_t value (which would require 64-bit operations to work +// correctly) it's enough to adjust the calculated number of days since epoch. +pub(crate) const EPOCH_ADJUSTMENT_DAYS: c_long = 719468; +// year to which the adjustment was made +pub(crate) const ADJUSTED_EPOCH_YEAR: c_int = 0; +// 1st March of year 0 is Wednesday +pub(crate) const ADJUSTED_EPOCH_WDAY: c_long = 3; +pub(crate) const DAYS_PER_ERA: c_long = (400 - 97) * 365 + 97 * 366; +pub(crate) const DAYS_PER_CENTURY: c_ulong = (100 - 24) * 365 + 24 * 366; +pub(crate) const DAYS_PER_4_YEARS: c_ulong = 3 * 365 + 366; +pub(crate) const DAYS_PER_YEAR: c_int = 365; +pub(crate) const DAYS_IN_JANUARY: c_int = 31; +pub(crate) const DAYS_IN_FEBRUARY: c_int = 28; +pub(crate) const YEARS_PER_ERA: c_int = 400; + +pub(crate) const SECSPERMIN: c_long = 60; +pub(crate) const MINSPERHOUR: c_long = 60; +pub(crate) const HOURSPERDAY: c_long = 24; +pub(crate) const SECSPERHOUR: c_long = SECSPERMIN * MINSPERHOUR; +pub(crate) const SECSPERDAY: c_long = SECSPERHOUR * HOURSPERDAY; +pub(crate) const DAYSPERWEEK: c_int = 7; + +pub(crate) const YEAR_BASE: c_int = 1900; + +pub(crate) const UTC: *const c_char = b"UTC\0" as *const u8 as *const c_char; diff --git a/src/time/src/helpers.rs b/src/time/src/helpers.rs new file mode 100644 index 0000000000000000000000000000000000000000..581c72eca1a5f15b2300e3d0fade8d46df982295 --- /dev/null +++ b/src/time/src/helpers.rs @@ -0,0 +1,40 @@ +use constants::*; +use platform::types::*; + +// compute year, month, day & day of year +// for description of this algorithm see +// http://howardhinnant.github.io/date_algorithms.html#civil_from_days +#[inline(always)] +pub(crate) fn civil_from_days(days: c_long) -> (c_int, c_int, c_int, c_int) { + let (era, year): (c_int, c_int); + let (erayear, mut yearday, mut month, day): (c_int, c_int, c_int, c_int); + let eraday: c_ulong; + + era = (if days >= 0 { + days + } else { + days - (DAYS_PER_ERA - 1) + } / DAYS_PER_ERA) as c_int; + eraday = (days - era as c_long * DAYS_PER_ERA) as c_ulong; + let a = eraday / (DAYS_PER_4_YEARS - 1); + let b = eraday / DAYS_PER_CENTURY; + let c = eraday / (DAYS_PER_ERA as c_ulong - 1); + erayear = ((eraday - a + b - c) / 365) as c_int; + let d = DAYS_PER_YEAR * erayear + erayear / 4 - erayear / 100; + yearday = (eraday - d as c_ulong) as c_int; + month = (5 * yearday + 2) / 153; + day = yearday - (153 * month + 2) / 5 + 1; + month += if month < 10 { 2 } else { -10 }; + year = ADJUSTED_EPOCH_YEAR + erayear + era * YEARS_PER_ERA + (month <= 1) as c_int; + yearday += if yearday >= DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY { + -(DAYS_PER_YEAR - DAYS_IN_JANUARY - DAYS_IN_FEBRUARY) + } else { + DAYS_IN_JANUARY + DAYS_IN_FEBRUARY + is_leap(erayear) + }; + return (year, month, day, yearday); +} + +#[inline(always)] +fn is_leap(y: c_int) -> c_int { + ((y % 4 == 0 && y % 100 != 0) || y % 400 == 0) as c_int +} diff --git a/src/time/src/lib.rs b/src/time/src/lib.rs index 5835d7014b7e01ae01e4ab067b27eff14a060c97..10538eba2e1500519de7d64ede543aa6500a6a65 100644 --- a/src/time/src/lib.rs +++ b/src/time/src/lib.rs @@ -1,37 +1,16 @@ //! time implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html #![no_std] +#![feature(const_fn)] extern crate platform; -use platform::types::*; +pub mod constants; +mod helpers; -#[cfg(target_os = "redox")] -pub const CLOCK_REALTIME: c_int = 1; -#[cfg(target_os = "redox")] -pub const CLOCK_MONOTONIC: c_int = 4; -#[cfg(target_os = "linux")] -pub const CLOCK_REALTIME: c_int = 0; -#[cfg(target_os = "linux")] -pub const CLOCK_MONOTONIC: c_int = 1; -#[cfg(target_os = "linux")] -pub const CLOCK_PROCESS_CPUTIME_ID: c_int = 2; -#[cfg(target_os = "linux")] -pub const CLOCK_THREAD_CPUTIME_ID: c_int = 3; -#[cfg(target_os = "linux")] -pub const CLOCK_MONOTONIC_RAW: c_int = 4; -#[cfg(target_os = "linux")] -pub const CLOCK_REALTIME_COARSE: c_int = 5; -#[cfg(target_os = "linux")] -pub const CLOCK_MONOTONIC_COARSE: c_int = 6; -#[cfg(target_os = "linux")] -pub const CLOCK_BOOTTIME: c_int = 7; -#[cfg(target_os = "linux")] -pub const CLOCK_REALTIME_ALARM: c_int = 8; -#[cfg(target_os = "linux")] -pub const CLOCK_BOOTTIME_ALARM: c_int = 9; -#[cfg(target_os = "linux")] -pub const CLOCK_TAI: c_int = 11; +use platform::types::*; +use constants::*; +use helpers::*; /* *#[repr(C)] @@ -56,6 +35,23 @@ pub struct tm { pub tm_zone: *const c_char, } +unsafe impl Sync for tm {} + +// The C Standard says that localtime and gmtime return the same pointer. +static mut TM: tm = 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, + tm_gmtoff: 0, + tm_zone: UTC, +}; + #[repr(C)] pub struct itimerspec { pub it_interval: timespec, @@ -105,8 +101,8 @@ pub extern "C" fn ctime_r(clock: *const time_t, buf: *mut c_char) -> *mut c_char } #[no_mangle] -pub extern "C" fn difftime(time1: time_t, time0: time_t) -> f64 { - unimplemented!(); +pub extern "C" fn difftime(time1: time_t, time0: time_t) -> c_double { + (time1 - time0) as c_double } #[no_mangle] @@ -116,12 +112,46 @@ pub extern "C" fn getdate(string: *const c_char) -> tm { #[no_mangle] pub extern "C" fn gmtime(timer: *const time_t) -> *mut tm { - unimplemented!(); + unsafe { gmtime_r(timer, &mut TM) } } #[no_mangle] pub extern "C" fn gmtime_r(clock: *const time_t, result: *mut tm) -> *mut tm { - unimplemented!(); + let (mut days, mut rem): (c_long, c_long); + let mut weekday: c_int; + let lcltime = unsafe { *clock }; + + days = lcltime / SECSPERDAY + EPOCH_ADJUSTMENT_DAYS; + rem = lcltime % SECSPERDAY; + if rem < 0 { + rem += SECSPERDAY; + days -= 1; + } + unsafe { + (*result).tm_hour = (rem / SECSPERHOUR) as c_int; + rem %= SECSPERHOUR; + (*result).tm_min = (rem / SECSPERMIN) as c_int; + (*result).tm_sec = (rem % SECSPERMIN) as c_int; + } + + weekday = ((ADJUSTED_EPOCH_WDAY + days) % DAYSPERWEEK as c_long) as c_int; + if weekday < 0 { + weekday += DAYSPERWEEK; + } + unsafe { (*result).tm_wday = weekday }; + + let (year, month, day, yearday) = civil_from_days(days); + unsafe { + (*result).tm_yday = yearday; + (*result).tm_year = year - YEAR_BASE; + (*result).tm_mon = month; + (*result).tm_mday = day; + + (*result).tm_isdst = 0; + (*result).tm_gmtoff = 0; + (*result).tm_zone = UTC; + } + result } #[no_mangle] diff --git a/src/time/src/linux.rs b/src/time/src/linux.rs new file mode 100644 index 0000000000000000000000000000000000000000..4f8f992025eed412294c00deafe805654d42f876 --- /dev/null +++ b/src/time/src/linux.rs @@ -0,0 +1,13 @@ +use platform::types::*; + +pub const CLOCK_REALTIME: c_int = 0; +pub const CLOCK_MONOTONIC: c_int = 1; +pub const CLOCK_PROCESS_CPUTIME_ID: c_int = 2; +pub const CLOCK_THREAD_CPUTIME_ID: c_int = 3; +pub const CLOCK_MONOTONIC_RAW: c_int = 4; +pub const CLOCK_REALTIME_COARSE: c_int = 5; +pub const CLOCK_MONOTONIC_COARSE: c_int = 6; +pub const CLOCK_BOOTTIME: c_int = 7; +pub const CLOCK_REALTIME_ALARM: c_int = 8; +pub const CLOCK_BOOTTIME_ALARM: c_int = 9; +pub const CLOCK_TAI: c_int = 11; diff --git a/src/time/src/redox.rs b/src/time/src/redox.rs new file mode 100644 index 0000000000000000000000000000000000000000..4e8a5d5e48793c2a23143160f7368471ca1c5bca --- /dev/null +++ b/src/time/src/redox.rs @@ -0,0 +1,4 @@ +use platform::types::*; + +pub const CLOCK_REALTIME: c_int = 1; +pub const CLOCK_MONOTONIC: c_int = 4; diff --git a/tests/.gitignore b/tests/.gitignore index a7b3b569fc0b3462b9084f92c62558afde3bafeb..44269d3e92186aa10cdb56b7b163932502bec64d 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -49,3 +49,4 @@ /waitpid /write /time +/gmtime diff --git a/tests/Makefile b/tests/Makefile index a5cdc494c2e557cb271afe3c11fbc55b18f5aa2c..63f40dff918adb84ac54dc6a91bedf9387ab071d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -43,7 +43,8 @@ EXPECT_BINS=\ unlink \ waitpid \ write \ - time + time \ + gmtime # Binaries that may generate varied output BINS=\ diff --git a/tests/gmtime.c b/tests/gmtime.c new file mode 100644 index 0000000000000000000000000000000000000000..59a71a79cd1d341ed1b8644074fbe2f7238e4128 --- /dev/null +++ b/tests/gmtime.c @@ -0,0 +1,28 @@ +#include <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int main(int argc, char** argv) { + time_t a = 0; + tm expected = { .tm_sec = 0, .tm_min = 0, .tm_hour = 0, .tm_mday = 1, .tm_year = 70, + .tm_wday = 4, .tm_yday = 0, .tm_isdst = 0, .tm_gmtoff = 0, .tm_zone = "UTC" }; + + tm *info = gmtime(&a); + if (info->tm_sec != expected.tm_sec || info->tm_min != expected.tm_min || + info->tm_hour != expected.tm_hour || info->tm_mday != expected.tm_mday || + info->tm_year != expected.tm_year || info->tm_wday != expected.tm_wday || + info->tm_yday != expected.tm_yday || info->tm_isdst != expected.tm_isdst || + info->tm_gmtoff != expected.tm_gmtoff || strcmp(info->tm_zone, expected.tm_zone) != 0) { + exit(1); + } + + if (info->tm_sec != expected.tm_sec || info->tm_min != expected.tm_min || + info->tm_hour != expected.tm_hour || info->tm_mday != expected.tm_mday || + info->tm_year != expected.tm_year || info->tm_wday != expected.tm_wday || + info->tm_yday != expected.tm_yday || info->tm_isdst != expected.tm_isdst || + info->tm_gmtoff != expected.tm_gmtoff || strcmp(info->tm_zone, expected.tm_zone) != 0) { + exit(1); + } + return 0; +}