diff --git a/src/time/cbindgen.toml b/src/time/cbindgen.toml index 7864a0a54cf419a29302a7702de84c5839b5f14c..b0f4a5f349f2d91a46975ab32c55c34ed45be6cf 100644 --- a/src/time/cbindgen.toml +++ b/src/time/cbindgen.toml @@ -1,4 +1,4 @@ -sys_includes = ["sys/types.h", "bits/timespec.h", "stdint.h"] +sys_includes = ["sys/types.h", "bits/timespec.h", "stdint.h", "stddef.h"] include_guard = "_TIME_H" language = "C" diff --git a/src/time/src/lib.rs b/src/time/src/lib.rs index 096bc599b589c77d0b6f44d845ac797c13d5d440..aba3d2f71ec97acfb35a92d60409d6e1c53246fe 100644 --- a/src/time/src/lib.rs +++ b/src/time/src/lib.rs @@ -1,13 +1,16 @@ //! time implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/time.h.html #![no_std] -#![feature(const_fn)] +#![feature(alloc, const_fn)] +#[macro_use] +extern crate alloc; extern crate errno; extern crate platform; pub mod constants; mod helpers; +mod strftime; use constants::*; use core::mem::transmute; @@ -286,7 +289,7 @@ pub unsafe extern "C" fn localtime_r(clock: *const time_t, t: *mut tm) -> *mut t } #[no_mangle] -pub unsafe extern "C" fn mktime(t: *mut tm) -> time_t { +pub unsafe extern "C" fn mktime(t: *const tm) -> time_t { let mut year = (*t).tm_year + 1900; let mut month = (*t).tm_mon; let mut day = (*t).tm_mday as i64 - 1; @@ -331,14 +334,14 @@ pub extern "C" fn nanosleep(rqtp: *const timespec, rmtp: *mut timespec) -> c_int platform::nanosleep(rqtp, rmtp) } -// #[no_mangle] -pub extern "C" fn strftime( +#[no_mangle] +pub unsafe extern "C" fn strftime( s: *mut c_char, maxsize: usize, format: *const c_char, - timptr: *const tm, -) -> usize { - unimplemented!(); + timeptr: *const tm, +) -> size_t { + strftime::strftime(true, &mut platform::UnsafeStringWriter(s as *mut u8), maxsize, format, timeptr) } // #[no_mangle] diff --git a/src/time/src/strftime.rs b/src/time/src/strftime.rs new file mode 100644 index 0000000000000000000000000000000000000000..58741bf05991a37371684c89d8897a7656694ae0 --- /dev/null +++ b/src/time/src/strftime.rs @@ -0,0 +1,144 @@ +use alloc::string::String; +use platform::Write; +use platform::types::*; +use tm; + +pub unsafe fn strftime<W: Write>(toplevel: bool, mut w: &mut W, maxsize: usize, mut format: *const c_char, t: *const tm) -> size_t { + let mut written = 0; + if toplevel { + // Reserve nul byte + written += 1; + } + macro_rules! w { + (reserve $amount:expr) => {{ + if written + $amount > maxsize { + return 0; + } + written += $amount; + }}; + (byte $b:expr) => {{ + w!(reserve 1); + w.write_u8($b); + }}; + (char $chr:expr) => {{ + w!(reserve $chr.len_utf8()); + w.write_char($chr); + }}; + (recurse $fmt:expr) => {{ + let mut fmt = String::with_capacity($fmt.len() + 1); + fmt.push_str($fmt); + fmt.push('\0'); + + let count = strftime(false, w, maxsize - written, fmt.as_ptr() as *mut c_char, t); + if count == 0 { + return 0; + } + written += count; + assert!(written <= maxsize); + }}; + ($str:expr) => {{ + w!(reserve $str.len()); + w.write_str($str); + }}; + ($fmt:expr, $($args:expr),+) => {{ + // Would use write!() if I could get the length written + w!(&format!($fmt, $($args),+)) + }}; + } + const WDAYS: [&'static str; 7] = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ]; + const MONTHS: [&'static str; 12] = [ + "January", + "Febuary", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" + ]; + + while *format != 0 { + if *format as u8 != b'%' { + w!(byte *format as u8); + format = format.offset(1); + continue; + } + + format = format.offset(1); + + if *format == b'E' || *format == b'O' { + // Ignore because these do nothing without locale + format = format.offset(1); + } + + match *format as u8 { + b'%' => w!(byte b'%'), + b'n' => w!(byte b'\n'), + b't' => w!(byte b'\t'), + b'a' => w!(&WDAYS[(*t).tm_wday as usize][..3]), + b'A' => w!(WDAYS[(*t).tm_wday as usize]), + b'b' | b'h' => w!(&MONTHS[(*t).tm_mon as usize][..3]), + b'B' => w!(MONTHS[(*t).tm_mon as usize]), + b'C' => { + let mut year = (*t).tm_year / 100; + // Round up + if (*t).tm_year % 100 != 0 { + year += 1; + } + w!("{:02}", year + 19); + }, + b'd' => w!("{:02}", (*t).tm_mday), + b'D' => w!(recurse "%m/%d/%y"), + b'e' => w!("{:2}", (*t).tm_mday), + b'F' => w!(recurse "%Y-%m-%d"), + b'H' => w!("{:02}", (*t).tm_hour), + b'I' => w!("{:02}", ((*t).tm_hour + 12 - 1) % 12 + 1), + b'j' => w!("{:03}", (*t).tm_yday), + b'k' => w!("{:2}", (*t).tm_hour), + b'l' => w!("{:2}", ((*t).tm_hour + 12 - 1) % 12 + 1), + b'm' => w!("{:02}", (*t).tm_mon + 1), + b'M' => w!("{:02}", (*t).tm_min), + b'p' => w!(if (*t).tm_hour < 12 { "AM" } else { "PM" }), + b'P' => w!(if (*t).tm_hour < 12 { "am" } else { "pm" }), + b'r' => w!(recurse "%I:%M:%S %p"), + b'R' => w!(recurse "%H:%M"), + b's' => w!("{}", ::mktime(t)), + b'S' => w!("{:02}", (*t).tm_sec), + b'T' => w!(recurse "%H:%M:%S"), + b'u' => w!("{}", ((*t).tm_wday + 7 - 1) % 7 + 1), + // I'm kinda confused. This is the musl code. For me this returns + // week 28 even though it's week 29. In fact, how *would* this even + // work if it's restricted by tm_yday and tm_wday (see the man + // page), considering years can start at different days of the + // week??? + b'U' => w!("{}", ((*t).tm_yday + 7 - (*t).tm_wday) / 7), + b'w' => w!("{}", (*t).tm_wday), + b'W' => w!("{}", ((*t).tm_yday + 7 - ((*t).tm_wday + 6) % 7) / 7), + b'y' => w!("{:02}", (*t).tm_year % 100), + b'Y' => w!("{}", (*t).tm_year + 1900), + b'z' => w!("+0000"), // TODO + b'Z' => w!("UTC"), // TODO + b'+' => w!(recurse "%a %b %d %T %Z %Y"), + _ => return 0 + } + + format = format.offset(1); + } + if toplevel { + // nul byte is already counted in written + w.write_u8(0); + } + written +} diff --git a/tests/.gitignore b/tests/.gitignore index 37fb9ac0fc4c48d54d04596aec103e5121e77007..264c70ee1cc3e9a531958568906de60e32458e74 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -56,6 +56,7 @@ string/strspn string/strstr string/strtok string/strtok_r +strftime strings system time diff --git a/tests/Makefile b/tests/Makefile index 2f7d9fbb98027e508c40b310739ab73cfd8f0263..de3feb5766a228d98d08cb1c6bbf49b8f78bb973 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -29,6 +29,7 @@ EXPECT_BINS=\ setjmp \ sleep \ sprintf \ + strftime \ strings \ stdio/fwrite \ stdio/all \ diff --git a/tests/expected/strftime.stderr b/tests/expected/strftime.stderr new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/expected/strftime.stdout b/tests/expected/strftime.stdout new file mode 100644 index 0000000000000000000000000000000000000000..afeeefccb51bffd1bf874a9aaec106d23d527b6d --- /dev/null +++ b/tests/expected/strftime.stdout @@ -0,0 +1,8 @@ +21: Tue Tuesday Jul July +17: The 21st century +12: 06:25:42 AM +12: 03:00:00 PM +6: 15:00 +16: 15 1531839600 2 +7: 197 28 +29: Tue Jul 17 15:00:00 UTC 2018 diff --git a/tests/strftime.c b/tests/strftime.c new file mode 100644 index 0000000000000000000000000000000000000000..cc308ad3a843fa76aa4b071c49fa8fd1c64c614a --- /dev/null +++ b/tests/strftime.c @@ -0,0 +1,20 @@ +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +void print(time_t timestamp, char* fmt) { + char* out = malloc(50); + size_t n = strftime(out, 50, fmt, localtime(×tamp)); + printf("%zu: %s\n", n, out); + free(out); +} +int main() { + print(1531808742, "%a %A %b %B"); + print(1531808742, "The %Cst century"); + print(1531808742, "%I:%M:%S %p"); + print(1531839600, "%r"); + print(1531839600, "%R"); + print(1531839600, "%H %s %u"); + print(1531839600, "%j %U"); + print(1531839600, "%+"); +}