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(&timestamp));
+    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