diff --git a/src/header/time/mod.rs b/src/header/time/mod.rs index 18924db419..7819112ac4 100644 --- a/src/header/time/mod.rs +++ b/src/header/time/mod.rs @@ -382,9 +382,8 @@ pub unsafe extern "C" fn mktime(timeptr: *mut tm) -> time_t { } }; - let offset = FixedOffset::east((*timeptr).tm_gmtoff as _); + let offset = get_offset((*timeptr).tm_gmtoff).unwrap(); let tz = time_zone(); - // Create DateTime let datetime = match offset.from_local_datetime(&naive_local) { MappedLocalTime::Single(datetime) => datetime, @@ -486,11 +485,12 @@ pub unsafe extern "C" fn timelocal(tm: *mut tm) -> time_t { None => return -1, }; + let tz_name = CString::new(tz.name()).unwrap(); (*tm).tm_wday = dt.weekday().num_days_from_sunday() as _; (*tm).tm_yday = dt.ordinal0() as _; // day of year starting at 0 (*tm).tm_isdst = dt.offset().dst_offset().num_hours() as _; (*tm).tm_gmtoff = dt.offset().fix().local_minus_utc() as _; - (*tm).tm_zone = UTC_STR.as_ptr() as *const c_char; + (*tm).tm_zone = tz_name.into_raw().cast(); dt.timestamp() } @@ -726,6 +726,15 @@ unsafe fn set_timezone( timezone = -c_long::from(ut_offset.fix().local_minus_utc()); } +#[inline(always)] +pub const fn get_offset(off: c_long) -> Option { + if off < 0 { + FixedOffset::west_opt(off as _) + } else { + FixedOffset::east_opt(off as _) + } +} + const fn blank_tm() -> tm { tm { tm_year: 0, diff --git a/src/header/time/strftime.rs b/src/header/time/strftime.rs index d7ab2cce55..fd6c21a11a 100644 --- a/src/header/time/strftime.rs +++ b/src/header/time/strftime.rs @@ -2,8 +2,11 @@ // Following https://pubs.opengroup.org/onlinepubs/7908799/xsh/strftime.html use alloc::string::String; -use super::tm; -use crate::platform::{self, types::*, WriteByte}; +use super::{get_offset, tm}; +use crate::{ + c_str::CStr, + platform::{self, types::*, WriteByte}, +}; // We use the langinfo constants use crate::header::langinfo::{ @@ -157,7 +160,7 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const b'I' => w!("{:02}", ((*t).tm_hour + 12 - 1) % 12 + 1), // Day of year: %j - b'j' => w!("{:03}", (*t).tm_yday), + b'j' => w!("{:03}", (*t).tm_yday + 1), // etc. b'k' => w!("{:2}", (*t).tm_hour), @@ -223,11 +226,22 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const // Full year: %Y b'Y' => w!("{}", (*t).tm_year + 1900), - // Timezone offset: %z => currently hard-coded "+0000" - b'z' => w!("+0000"), // TODO: Add real timezone support + // Timezone offset: %z + b'z' => { + let offset = (*t).tm_gmtoff; + let (sign, offset) = if offset < 0 { + ('-', -offset) + } else { + ('+', offset) + }; + let mins = offset.div_euclid(60); + let min = mins.rem_euclid(60); + let hour = mins.div_euclid(60); + w!("{}{:02}{:02}", sign, hour, min) + } - // Timezone name: %Z => currently "UTC" - b'Z' => w!("UTC"), // TODO: Add real timezone name support + // Timezone name: %Z + b'Z' => w!("{}", CStr::from_ptr((*t).tm_zone).to_str().unwrap()), // Date+time+TZ: %+ b'+' => w!(recurse "%a %b %d %T %Z %Y"), @@ -256,8 +270,6 @@ pub unsafe fn strftime(w: &mut W, format: *const c_char, t: *const /// https://en.wikipedia.org/wiki/ISO_week_date fn weeks_per_year(year: c_int) -> c_int { let year = year as f64; - // TODO: This function can be made const on newer Rust compilers but this line prevents it on - // our version in CI let p_y = (year + (year / 4.) - (year / 100.) + (year / 400.)) as c_int % 7; if p_y == 4 { 53 diff --git a/tests/expected/bins_dynamic/time/strftime.stdout b/tests/expected/bins_dynamic/time/strftime.stdout index 83638a54bb..7d12e13954 100644 --- a/tests/expected/bins_dynamic/time/strftime.stdout +++ b/tests/expected/bins_dynamic/time/strftime.stdout @@ -4,7 +4,7 @@ 11: 03:00:00 PM 5: 15:00 15: 15 1531839600 2 -6: 197 28 +6: 198 28 28: Tue Jul 17 15:00:00 UTC 2018 0: Tue Aug 07 19:17:11 UTC 2018Tue Aug 07 19:17:11 U 53 diff --git a/tests/expected/bins_static/time/strftime.stdout b/tests/expected/bins_static/time/strftime.stdout index 83638a54bb..7d12e13954 100644 --- a/tests/expected/bins_static/time/strftime.stdout +++ b/tests/expected/bins_static/time/strftime.stdout @@ -4,7 +4,7 @@ 11: 03:00:00 PM 5: 15:00 15: 15 1531839600 2 -6: 197 28 +6: 198 28 28: Tue Jul 17 15:00:00 UTC 2018 0: Tue Aug 07 19:17:11 UTC 2018Tue Aug 07 19:17:11 U 53 diff --git a/tests/time/strftime.c b/tests/time/strftime.c index 898fcfdebf..629f86f838 100644 --- a/tests/time/strftime.c +++ b/tests/time/strftime.c @@ -1,16 +1,110 @@ #include #include #include - +#include #include "test_helpers.h" #define PRINT_BUFSZ 50 #define V_BUFSZ 3 +#define BUFS 30 void print(time_t timestamp, char* fmt) { char out[PRINT_BUFSZ] = {0}; size_t n = strftime(out, PRINT_BUFSZ, fmt, localtime(×tamp)); printf("%zu: %s\n", n, out); + +} + +void strftime_mix(void) { + setenv("TZ", "Australia/Melbourne", 1); + tzset(); + + // Initialize a struct tm with a known fixed date and time. + // December 31, 2020 23:59:59, which is a Thursday. + struct tm t = {0}; + t.tm_year = 2020 - 1900; // Years since 1900. + t.tm_mon = 11; // December (0-based, so 11 means December). + t.tm_mday = 31; + t.tm_hour = 23; + t.tm_min = 59; + t.tm_sec = 59; + t.tm_wday = 4; // Thursday (0 = Sunday, so Thursday = 4). + t.tm_yday = 365; + t.tm_gmtoff = 39600; // GMT offset for AEDT (UTC+11). + t.tm_isdst = 11; // Daylight saving time is in effect. + t.tm_zone = "AEDT"; // Time zone name. + char buf[BUFS]; + + strftime(buf, BUFS, "%a", &t); + assert(strcmp(buf, "Thu") == 0); + + strftime(buf, BUFS, "%A", &t); + assert(strcmp(buf, "Thursday") == 0); + + strftime(buf, BUFS, "%b", &t); + assert(strcmp(buf, "Dec") == 0); + + strftime(buf, BUFS, "%B", &t); + assert(strcmp(buf, "December") == 0); + + strftime(buf, BUFS, "%d", &t); + assert(strcmp(buf, "31") == 0); + + strftime(buf, BUFS, "%H", &t); + assert(strcmp(buf, "23") == 0); + + strftime(buf, BUFS, "%I", &t); + assert(strcmp(buf, "11") == 0); + + // Day of the year as a zero-padded decimal number (001-366). + strftime(buf, BUFS, "%j", &t); + // Even though t.tm_yday is 365 (zero-based), %j is one-based: "366" + assert(strcmp(buf, "366") == 0); + + strftime(buf, BUFS, "%m", &t); + assert(strcmp(buf, "12") == 0); + + strftime(buf, BUFS, "%M", &t); + assert(strcmp(buf, "59") == 0); + + strftime(buf, BUFS, "%p", &t); + assert(strcmp(buf, "PM") == 0); + + strftime(buf, BUFS, "%S", &t); + assert(strcmp(buf, "59") == 0); + + strftime(buf, BUFS, "%U", &t); + assert(strcmp(buf, "52") == 0); + + strftime(buf, BUFS, "%w", &t); + assert(strcmp(buf, "4") == 0); + + strftime(buf, BUFS, "%W", &t); + assert(strcmp(buf, "52") == 0); + + strftime(buf, BUFS, "%y", &t); + assert(strcmp(buf, "20") == 0); + + strftime(buf, BUFS, "%Y", &t); + assert(strcmp(buf, "2020") == 0); + + strftime(buf, BUFS, "%Z", &t); + assert(strcmp(buf, "AEDT") == 0); + + strftime(buf, BUFS, "%z", &t); + assert(strcmp(buf, "+1100") == 0); + + strftime(buf, BUFS, "%F", &t); + assert(strcmp(buf, "2020-12-31") == 0); + + strftime(buf, BUFS, "%R", &t); + assert(strcmp(buf, "23:59") == 0); + + strftime(buf, BUFS, "%r", &t); + assert(strcmp(buf, "11:59:59 PM") == 0); + + strftime(buf, BUFS, "%T", &t); + assert(strcmp(buf, "23:59:59") == 0); } void test_v(void) { @@ -67,5 +161,7 @@ int main(void) { // ISO-8601 tests test_v(); + strftime_mix(); + return EXIT_SUCCESS; }