From b83d1c7ff0d8bad8c446ec3637d5424171011cb9 Mon Sep 17 00:00:00 2001 From: jD91mZM2 <me@krake.one> Date: Tue, 17 Jul 2018 16:47:33 +0200 Subject: [PATCH] strftime :D --- src/time/cbindgen.toml | 2 +- src/time/src/lib.rs | 17 ++-- src/time/src/strftime.rs | 144 +++++++++++++++++++++++++++++++++ tests/.gitignore | 1 + tests/Makefile | 1 + tests/expected/strftime.stderr | 0 tests/expected/strftime.stdout | 8 ++ tests/strftime.c | 20 +++++ 8 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 src/time/src/strftime.rs create mode 100644 tests/expected/strftime.stderr create mode 100644 tests/expected/strftime.stdout create mode 100644 tests/strftime.c diff --git a/src/time/cbindgen.toml b/src/time/cbindgen.toml index 7864a0a5..b0f4a5f3 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 096bc599..aba3d2f7 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 00000000..58741bf0 --- /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 37fb9ac0..264c70ee 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 2f7d9fbb..de3feb57 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 00000000..e69de29b diff --git a/tests/expected/strftime.stdout b/tests/expected/strftime.stdout new file mode 100644 index 00000000..afeeefcc --- /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 00000000..cc308ad3 --- /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, "%+"); +} -- GitLab