From a9ea2ef64eb72c8af02bd55fd1a221b4bc0588b4 Mon Sep 17 00:00:00 2001
From: jD91mZM2 <me@krake.one>
Date: Fri, 13 Jul 2018 09:13:46 +0200
Subject: [PATCH] Fixup time & support negative & mktime

---
 src/time/src/lib.rs             | 146 +++++++++++++++++++++++---------
 tests/.gitignore                |   2 +
 tests/Makefile                  |   2 +
 tests/expected/localtime.stderr |   0
 tests/expected/localtime.stdout |   6 ++
 tests/expected/mktime.stderr    |   0
 tests/expected/mktime.stdout    |   6 ++
 tests/localtime.c               |  18 ++++
 tests/mktime.c                  |  22 +++++
 9 files changed, 162 insertions(+), 40 deletions(-)
 create mode 100644 tests/expected/localtime.stderr
 create mode 100644 tests/expected/localtime.stdout
 create mode 100644 tests/expected/mktime.stderr
 create mode 100644 tests/expected/mktime.stdout
 create mode 100644 tests/localtime.c
 create mode 100644 tests/mktime.c

diff --git a/src/time/src/lib.rs b/src/time/src/lib.rs
index 43014c27..878df02c 100644
--- a/src/time/src/lib.rs
+++ b/src/time/src/lib.rs
@@ -198,70 +198,136 @@ pub unsafe extern "C" fn localtime(clock: *const time_t) -> *mut tm {
     localtime_r(clock, &mut TM)
 }
 
+const MONTH_DAYS: [[c_int; 12]; 2] = [
+    // Non-leap years:
+    [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+    // Leap years:
+    [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+];
+
+fn leap_year(year: c_int) -> bool {
+    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
+}
+
 #[no_mangle]
-pub unsafe extern "C" fn localtime_r(clock: *const time_t, r: *mut tm) -> *mut tm {
-    fn leap_year(year: c_int) -> bool {
-        year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
+pub unsafe extern "C" fn localtime_r(clock: *const time_t, t: *mut tm) -> *mut tm {
+    let clock = *clock;
+
+    let mut day = (clock / (60 * 60 * 24)) as c_int;
+    if clock < 0 && clock % (60 * 60 * 24) != 0 {
+        // -1 because for negative values round upwards
+        // -0.3 == 0, but we want -1
+        day -= 1;
     }
-    let mut clock = *clock;
 
-    if clock < 0 {
-        unimplemented!("localtime_r with a negative time is to be implemented");
+    (*t).tm_sec = (clock % 60) as c_int;
+    (*t).tm_min = ((clock / 60) % 60) as c_int;
+    (*t).tm_hour = ((clock / (60 * 60)) % 24) as c_int;
+
+    while (*t).tm_sec < 0 {
+        (*t).tm_sec += 60;
+        (*t).tm_min -= 1;
+    }
+    while (*t).tm_min < 0 {
+        (*t).tm_min += 60;
+        (*t).tm_hour -= 1;
+    }
+    while (*t).tm_hour < 0 {
+        (*t).tm_hour += 24;
     }
 
-    let mut days = (clock / (60 * 60 * 24)) as c_int;
+    // Jan 1th was a thursday, 4th of a zero-indexed week.
+    (*t).tm_wday = (day + 4) % 7;
+    if (*t).tm_wday < 0 {
+        (*t).tm_wday += 7;
+    }
 
-    // Epoch, Jan 1 1970, was on a thursday.
-    // Jan 5th was a monday.
-    (*r).tm_wday = (days + 4) % 7;
+    let mut year = 1970;
+    if day < 0 {
+        while day < 0 {
+            let days_in_year = if leap_year(year) { 366 } else { 365 };
 
-    (*r).tm_year = 1970;
+            day += days_in_year;
+            year -= 1;
+        }
+        (*t).tm_year = year - 1900;
+        (*t).tm_yday = day + 1;
+    } else {
+        loop {
+            let days_in_year = if leap_year(year) { 366 } else { 365 };
+
+            if day < days_in_year {
+                break;
+            }
+
+            day -= days_in_year;
+            year += 1;
+        }
+        (*t).tm_year = year - 1900;
+        (*t).tm_yday = day;
+    }
 
+    let leap = if leap_year(year) { 1 } else { 0 };
+    (*t).tm_mon = 0;
     loop {
-        let days_in_year = if leap_year((*r).tm_year) { 366 } else { 365 };
+        let days_in_month = MONTH_DAYS[leap][(*t).tm_mon as usize];
 
-        if days < days_in_year {
+        if day < days_in_month {
             break;
         }
 
-        days -= days_in_year;
-        (*r).tm_year += 1;
+        day -= days_in_month;
+        (*t).tm_mon += 1;
     }
+    (*t).tm_mday = 1 + day as c_int;
+    (*t).tm_isdst = 0;
+
+    t
+}
 
-    (*r).tm_yday = days;
+#[no_mangle]
+pub unsafe extern "C" fn mktime(t: *mut 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;
 
-    (*r).tm_sec = (clock % 60) as c_int;
-    (*r).tm_min = ((clock % (60 * 60)) / 60) as c_int;
-    (*r).tm_hour = (clock / (60 * 60)) as c_int;
+    if year < 1970 {
+        day = MONTH_DAYS[if leap_year(year) { 1 } else { 0 }][(*t).tm_mon as usize] as i64 - day;
 
-    const MONTH_DAYS: [[c_int; 12]; 2] = [
-        // Non-leap years:
-        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
-        // Leap years:
-        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
-    ];
+        while year < 1969 {
+            year += 1;
+            day += if leap_year(year) { 366 } else { 365 };
+        }
 
-    let leap = if leap_year((*r).tm_year) { 1 } else { 0 };
+        let leap = if leap_year(year) { 1 } else { 0 };
 
-    loop {
-        let days_in_month = MONTH_DAYS[leap][(*r).tm_mon as usize];
+        while month < 11 {
+            month += 1;
+            day += MONTH_DAYS[leap][month as usize] as i64;
+        }
 
-        if days < (*r).tm_mon {
-            break;
+        -(day * (60 * 60 * 24)
+            - (((*t).tm_hour as i64) * (60 * 60)
+                + ((*t).tm_min as i64) * 60
+                + (*t).tm_sec as i64))
+    } else {
+        while year > 1970 {
+            day += if leap_year(year) { 366 } else { 365 };
+            year -= 1;
         }
 
-        days -= days_in_month;
-        (*r).tm_mon += 1;
-    }
-    (*r).tm_mday = days as c_int;
-    (*r).tm_isdst = 0;
+        let leap = if leap_year(year) { 1 } else { 0 };
 
-    r
-}
+        while month > 0 {
+            day += MONTH_DAYS[leap][month as usize] as i64;
+            month -= 1;
+        }
 
-// #[no_mangle]
-pub extern "C" fn mktime(timeptr: *mut tm) -> time_t {
-    unimplemented!();
+        (day * (60 * 60 * 24)
+            + ((*t).tm_hour as i64) * (60 * 60)
+            + ((*t).tm_min as i64) * 60
+            + (*t).tm_sec as i64)
+    }
 }
 
 #[no_mangle]
diff --git a/tests/.gitignore b/tests/.gitignore
index afb0d2f8..fb6577e9 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -21,8 +21,10 @@
 /getid
 /link
 /locale
+/localtime
 /math
 /mem
+/mktime
 /pipe
 /printf
 /rmdir
diff --git a/tests/Makefile b/tests/Makefile
index 0957b27f..eec3cb70 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -16,8 +16,10 @@ EXPECT_BINS=\
 	ftruncate \
 	getc_unget \
 	locale \
+	localtime \
 	math \
 	mem \
+	mktime \
 	pipe \
 	printf \
 	rename \
diff --git a/tests/expected/localtime.stderr b/tests/expected/localtime.stderr
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/expected/localtime.stdout b/tests/expected/localtime.stdout
new file mode 100644
index 00000000..12a3072c
--- /dev/null
+++ b/tests/expected/localtime.stdout
@@ -0,0 +1,6 @@
+Year 69, Day of year: 333, Month 10, Day of month: 29, Day of week: 6, 0:0:0
+Year 69, Day of year: 365, Month 11, Day of month: 31, Day of week: 3, 0:0:0
+Year 69, Day of year: 365, Month 11, Day of month: 31, Day of week: 3, 23:51:40
+Year 70, Day of year: 0, Month 0, Day of month: 1, Day of week: 4, 0:0:0
+Year 118, Day of year: 193, Month 6, Day of month: 13, Day of week: 5, 4:9:10
+Fri Jul 13 06:03:43 2018
diff --git a/tests/expected/mktime.stderr b/tests/expected/mktime.stderr
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/expected/mktime.stdout b/tests/expected/mktime.stdout
new file mode 100644
index 00000000..c9b309e6
--- /dev/null
+++ b/tests/expected/mktime.stdout
@@ -0,0 +1,6 @@
+31536000
+-2851200 = -2851200
+-86400 = -86400
+-500 = -500
+0 = 0
+1531454950 = 1531454950
diff --git a/tests/localtime.c b/tests/localtime.c
new file mode 100644
index 00000000..edce6cf6
--- /dev/null
+++ b/tests/localtime.c
@@ -0,0 +1,18 @@
+#include <stdio.h>
+#include <time.h>
+
+int main() {
+    int day = 60 * 60 * 24;
+    time_t inputs[] = { -(day * 33), -day, -500, 0, 1531454950 };
+    for (int i = 0; i < 5; i += 1) {
+        struct tm* t = localtime(&inputs[i]);
+
+        printf(
+            "Year %d, Day of year: %d, Month %d, Day of month: %d, Day of week: %d, %d:%d:%d\n",
+            t->tm_year, t->tm_yday, t->tm_mon, t->tm_mday, t->tm_wday, t->tm_hour, t->tm_min, t->tm_sec
+        );
+    }
+
+    time_t input = 1531461823;
+    fputs(ctime(&input), stdout); // Omit newline
+}
diff --git a/tests/mktime.c b/tests/mktime.c
new file mode 100644
index 00000000..17eb30b1
--- /dev/null
+++ b/tests/mktime.c
@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <time.h>
+
+int main() {
+    struct tm t = {};
+
+    t.tm_year = 71;
+    t.tm_mday = 1;
+
+    printf("%ld\n", mktime(&t));
+
+    int day = 60 * 60 * 24;
+    time_t inputs[] = { -(day * 33), -day, -500, 0, 1531454950 };
+    for (int i = 0; i < 5; i += 1) {
+        struct tm* t2 = localtime(&inputs[i]);
+
+        printf("%ld = %ld\n", inputs[i], mktime(t2));
+        if (inputs[i] != mktime(t2)) {
+            puts("Failed!");
+        }
+    }
+}
-- 
GitLab