diff --git a/CMakeLists.txt b/CMakeLists.txt index b7598d2..a69faf5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,7 +192,15 @@ set(SOURCES src/libc/string/strstr.c src/libc/string/strstr_base.c src/libc/string/strtok.c - src/libc/string/strxfrm.c) + src/libc/string/strxfrm.c + # time + src/libc/time/asctime.c + src/libc/time/ctime.c + src/libc/time/difftime.c + src/libc/time/gmtime.c + src/libc/time/localtime.c + src/libc/time/mktime.c + src/libc/time/strftime.c) # Silence extended warnings on Grisu2b code set_source_files_properties(3rdparty/grisu2b_59_56/grisu2b_59_56.c PROPERTIES @@ -239,9 +247,13 @@ endif() if(gint IN_LIST TARGET_FOLDERS) list(APPEND SOURCES + # stdlib src/libc/stdlib/target/gint/free.c src/libc/stdlib/target/gint/malloc.c - src/libc/stdlib/target/gint/realloc.c) + src/libc/stdlib/target/gint/realloc.c + # time + src/libc/time/target/gint/clock.c + src/libc/time/target/gint/time.c) endif() if(casiowin-fx IN_LIST TARGET_FOLDERS) diff --git a/STATUS b/STATUS index baa03f6..b1b4c6f 100644 --- a/STATUS +++ b/STATUS @@ -157,16 +157,16 @@ DONE: Function/symbol/macro is defined, builds, links, and is tested 7.22 => GCC 7.23 -! 7.23.1 Components of time: TODO -! 7.23.2.1 clock: TODO -! 7.23.2.2 difftime: TODO -! 7.23.2.3 mktime: TODO -! 7.23.2.4 time: TODO -! 7.23.3.1 asctime: TODO -! 7.23.3.2 ctime: TODO -! 7.23.3.3 gmtime: TODO -! 7.23.3.4 localtime: TODO -! 7.23.3.5 strftime: TODO + 7.23.1 Components of time: DONE + 7.23.2.1 clock: DONE + 7.23.2.2 difftime: DONE + 7.23.2.3 mktime: DONE (DST flag ignored) + 7.23.2.4 time: DONE + 7.23.3.1 asctime: DONE + 7.23.3.2 ctime: DONE + 7.23.3.3 gmtime: DONE + 7.23.3.4 localtime: DONE (No timezones; same as gmtime) + 7.23.3.5 strftime: DONE (No %g/%G/%U/%V/%W; timezones %z/%Z empty) 7.24 TODO (not a priority) @@ -184,6 +184,7 @@ What if we wanted to support more locales? -> Fix the "TODO: locale: ..." messages wherever assumptions on the locale are made in the code -> Properly implement strcoll() and strxfrm() +-> Add support in strftime() # Supporting text and binary files (newline translation) @@ -195,3 +196,8 @@ This requires all the wide-char functions but also updating fpos_t to be a structure with at least some mbstate_t member (7.19.2ยง6). I really don't want to do that. Use multi-byte functions with UTF-8. + +# Supporting timezones + +-> Update localtime() +-> Add some timezone API diff --git a/include/time.h b/include/time.h index a1d9d63..99a0b9d 100644 --- a/include/time.h +++ b/include/time.h @@ -5,28 +5,65 @@ extern "C" { #endif -/* Number of ticks per second in a clock_t value. */ +#include +#include + +/* Number of ticks per second in a clock_t value. This is not necessarily the + full precision (eg. the RTC has only 128 units per second). */ #define CLOCKS_PER_SEC 1000000 -/* Type used to represent process CPU time; unit is CLOCKS_PER_SEC. */ +/* Represent process CPU time; unit is CLOCKS_PER_SEC. */ typedef uint64_t clock_t; -/* Type used to represent a number of seconds since Epoch. */ -typedef uint64_t time_t; +/* Represent a number of seconds since 1970-01-01 00:00:00 +0000 (UTC). */ +typedef int64_t time_t; /* Broken-down time. */ struct tm { - int tm_sec; - int tm_min; - int tm_hour; - int tm_mday; - int tm_mon; - int tm_year; - int tm_wday; - int tm_yday; - int tm_isdst; + int tm_sec; /* Seconds (0..60) */ + int tm_min; /* Minutes (0..59) */ + int tm_hour; /* Hours (0..23) */ + int tm_mday; /* Day of month (1..31) */ + int tm_mon; /* Month (0..11) */ + int tm_year; /* Years since 1900 */ + int tm_wday; /* Day of week, starting Sunday (0..6) */ + int tm_yday; /* Day of year (0..365) */ + int tm_isdst; /* Daylight Saving Time flag */ }; +/* Returns CPU time used by the program (in number of CLOCKS_PER_SEC). */ +extern clock_t clock(void); + +/* Time elapsed between __start and __end, in seconds. */ +double difftime(time_t __end, time_t __start); + +/* Normalizes __time and returns associated timestamp. + TODO: Currently ignores the [tm_isdst] field. */ +extern time_t mktime(struct tm *__time); + +/* Determine current timestamp; also set it in __timeptr if non-NULL. */ +extern time_t time(time_t *__timeptr); + +/* Text representation, like "Sun Sep 16 01:03:52 1973\n". The returned string + is statically allocated and is overwritten by every call. */ +extern char *asctime(const struct tm *__time); + +/* Convert calendar time to asctime()'s text representation. */ +extern char *ctime(const time_t *__time); + +/* Convert calendar time to broken-down time as UTC. */ +extern struct tm *gmtime(const time_t *__time); + +/* Convert calendar time to broken-down local time. + TODO: We don't have timezones so this always returns UTC. */ +extern struct tm *localtime(const time_t *time); + +/* Formats __time according to the specified format; similar to snprintf(). + TODO: %g, %G, %V (week-based year), and %U, %W (week number) are not + supported and substituted by "??". %z and %Z output nothing. */ +size_t strftime(char * restrict __s, size_t __maxsize, + const char * restrict __format, const struct tm * restrict __time); + #ifdef __cplusplus } #endif diff --git a/src/libc/time/asctime.c b/src/libc/time/asctime.c new file mode 100644 index 0000000..d34404e --- /dev/null +++ b/src/libc/time/asctime.c @@ -0,0 +1,21 @@ +#include +#include + +static const char wday_name[8][3] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "???" +}; +static const char mon_name[13][3] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" +}; + +char *asctime(const struct tm *time) +{ + int wday = ((unsigned int)time->tm_wday < 7) ? time->tm_wday : 7; + int mon = ((unsigned int)time->tm_mon < 12) ? time->tm_mon : 12; + static char str[26]; + sprintf(str, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", + wday_name[wday], mon_name[mon], time->tm_mday, time->tm_hour, + time->tm_min, time->tm_sec, time->tm_year + 1900); + return str; +} diff --git a/src/libc/time/ctime.c b/src/libc/time/ctime.c new file mode 100644 index 0000000..b69998c --- /dev/null +++ b/src/libc/time/ctime.c @@ -0,0 +1,6 @@ +#include + +char *ctime(const time_t *time) +{ + return asctime(localtime(time)); +} diff --git a/src/libc/time/difftime.c b/src/libc/time/difftime.c new file mode 100644 index 0000000..828ad65 --- /dev/null +++ b/src/libc/time/difftime.c @@ -0,0 +1,6 @@ +#include + +double difftime(time_t t1, time_t t0) +{ + return (double)(t1 - t0); +} diff --git a/src/libc/time/gmtime.c b/src/libc/time/gmtime.c new file mode 100644 index 0000000..cf2a27e --- /dev/null +++ b/src/libc/time/gmtime.c @@ -0,0 +1,20 @@ +#include +#include "timeutil.h" + +struct tm *gmtime(const time_t *timeptr) +{ + static struct tm t; + + /* Specify Epoch + *timeptr seconds */ + t.tm_year = 1970 - 1900; + t.tm_mon = 0; + t.tm_mday = 1; + t.tm_hour = 0; + t.tm_min = 0; + t.tm_sec = *timeptr; + t.tm_isdst = 0; + + /* Then let mktime() do all the hard work */ + mktime(&t); + return &t; +} diff --git a/src/libc/time/localtime.c b/src/libc/time/localtime.c new file mode 100644 index 0000000..a68f878 --- /dev/null +++ b/src/libc/time/localtime.c @@ -0,0 +1,7 @@ +#include + +/* TODO: localtime: No timezone specification is supported */ +struct tm *localtime(const time_t *time) +{ + return gmtime(time); +} diff --git a/src/libc/time/mktime.c b/src/libc/time/mktime.c new file mode 100644 index 0000000..2157e02 --- /dev/null +++ b/src/libc/time/mktime.c @@ -0,0 +1,58 @@ +#include +#include "timeutil.h" + +/* TODO: mktime: DST not supported */ +time_t mktime(struct tm *time) +{ + /* Normalize time */ + time->tm_min += (time->tm_sec / 60); + time->tm_hour += (time->tm_min / 60); + time->tm_mday += (time->tm_hour / 24); + time->tm_sec %= 60; + time->tm_min %= 60; + time->tm_hour %= 24; + + /* Normalize date */ + int days = daysin(time->tm_mon, time->tm_year + 1900); + while(time->tm_mday > days) { + time->tm_mday -= days; + if(++time->tm_mon == 12) { + time->tm_mon = 0; + time->tm_year++; + } + days = daysin(time->tm_mon, time->tm_year + 1900); + } + + /* Determine day in year */ + time->tm_yday = time->tm_mday - 1; + for(int i = 0; i < time->tm_mon; i++) + time->tm_yday += daysin(i, time->tm_year + 1900); + + /* Determine day in week. The calendar has a period of 400 years and + 1601-01-01 was a Monday. */ + + /* Years elapsed since last 400n+1 year (1601-2001-etc). */ + int yr = (time->tm_year + 1900 - 1) % 400; + /* Leap years during this period */ + int leaps = (yr / 4) - (yr >= 100) - (yr >= 200) - (yr >= 300); + /* Days elapsed since 01-01 on the last 400n+1 year */ + days = 365 * yr + leaps + time->tm_yday; + /* Current day of week (1 is Monday on 01-01 of last 400n+1 year) */ + time->tm_wday = (1 + days) % 7; + + /* We don't determine DST at the targeted time */ + time->tm_isdst = 0; + + /* Number of periods elapsed since 1601-01-01 (may be negative) */ + int periods = (time->tm_year + 1900 - 1601) / 400; + periods -= (time->tm_year + 1900 < 1601); + /* Days elapsed since 1970-01-01; the calendar period is 146097 days + and there are 134774 days between 1601-01-01 and 1970-01-01 */ + days += 146097 * periods - 134774; + + time_t t = days; + t = 24 * t + time->tm_hour; + t = 60 * t + time->tm_min; + t = 60 * t + time->tm_sec; + return t; +} diff --git a/src/libc/time/strftime.c b/src/libc/time/strftime.c new file mode 100644 index 0000000..6a6f91a --- /dev/null +++ b/src/libc/time/strftime.c @@ -0,0 +1,94 @@ +#include +#include + +static const char *day_names[7] = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", + "Saturday", +}; +static const char *month_names[12] = { + "January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", "December", +}; + +/* Perform a call to snprintf() to print a value */ +#define do_snprintf(...) { \ + size_t _written = snprintf(s+len, n-len, __VA_ARGS__); \ + len += _written; \ + if(len >= n) goto full; \ +}; break +/* Perform a sub-call to strftime to expand a complex format */ +#define do_strftime(fmt) { \ + size_t _written = strftime2(s+len, n-len, fmt, time); \ + if(_written == 0) goto full; \ + len += _written; \ +}; break + +size_t strftime2(char * restrict s, size_t n, const char * restrict format, + const struct tm * restrict time) +{ + size_t len = 0; + + while(*format) { + if(*format != '%') { + s[len++] = *format++; + if(len >= n) goto full; + continue; + } + format++; + int c = *format++; + + /* In the "C" locale, ignore modifiers E and O */ + if(c == 'E') c = *format++; + else if(c == 'O') c = *format++; + + switch(c) { + case 'a': do_snprintf("%.3s", day_names[time->tm_wday]); + case 'A': do_snprintf("%s", day_names[time->tm_wday]); + case 'b': do_snprintf("%.3s", month_names[time->tm_mon]); + case 'B': do_snprintf("%s", month_names[time->tm_mon]); + case 'c': do_strftime("%a %b %e %T %Y"); + case 'C': do_snprintf("%02d", (time->tm_year + 1900) / 100); + case 'd': do_snprintf("%02d", time->tm_mday); + case 'D': do_strftime("%m/%d/%y"); + case 'e': do_snprintf("%2d", time->tm_mday); + case 'F': do_strftime("%Y-%m-%d"); + /* TODO: strftime: Support week-based year */ + case 'g': do_snprintf("??"); + case 'G': do_snprintf("????"); + case 'h': do_strftime("%b"); + case 'H': do_snprintf("%02d", time->tm_hour); + case 'I': do_snprintf("%02d", (time->tm_hour+11)%12 + 1); + case 'j': do_snprintf("%03d", time->tm_yday + 1); + case 'm': do_snprintf("%02d", time->tm_mon + 1); + case 'M': do_snprintf("%02d", time->tm_min); + case 'n': do_snprintf("\n"); + case 'p': do_snprintf(time->tm_hour < 12 ? "AM" : "PM"); + case 'r': do_strftime("%I:%M:%S %p"); + case 'R': do_strftime("%H:%M"); + case 'S': do_snprintf("%02d", time->tm_sec); + case 't': do_snprintf("\t"); + case 'T': do_strftime("%H:%M:%S"); + case 'u': do_snprintf("%d", (time->tm_wday+6) % 7 + 1); + /* TODO: strftime: Support week number */ + case 'U': do_snprintf("??"); + case 'V': do_snprintf("??"); + case 'w': do_snprintf("%d", time->tm_wday); + case 'W': do_snprintf("??"); + case 'x': do_strftime("%m/%d/%y"); + case 'X': do_strftime("%T"); + case 'y': do_snprintf("%02d", time->tm_year % 100); + case 'Y': do_snprintf("%d", time->tm_year + 1900); + /* TODO: strftime: Support timezones */ + case 'z': break; + case 'Z': break; + case '%': do_snprintf("%%"); + } + } + + s[len] = 0; + return len; + +full: + s[n-1] = 0; + return 0; +} diff --git a/src/libc/time/target/gint/clock.c b/src/libc/time/target/gint/clock.c new file mode 100644 index 0000000..abcec26 --- /dev/null +++ b/src/libc/time/target/gint/clock.c @@ -0,0 +1,20 @@ +#include +#include +#include + +static clock_t clock_init; + +static clock_t clock_abs(void) +{ + return (CLOCKS_PER_SEC * (uint64_t)rtc_ticks()) / 128; +} + +GCONSTRUCTOR static void clock_initialize(void) +{ + clock_init = clock_abs(); +} + +clock_t clock(void) +{ + return clock_abs() - clock_init; +} diff --git a/src/libc/time/target/gint/time.c b/src/libc/time/target/gint/time.c new file mode 100644 index 0000000..34ce854 --- /dev/null +++ b/src/libc/time/target/gint/time.c @@ -0,0 +1,23 @@ +#include +#include + +time_t time(time_t *timeptr) +{ + rtc_time_t rtc; + struct tm tm; + time_t calendar; + + rtc_get_time(&rtc); + tm.tm_sec = rtc.seconds; + tm.tm_min = rtc.minutes; + tm.tm_hour = rtc.hours; + tm.tm_mday = rtc.month_day; + tm.tm_mon = rtc.month; + tm.tm_year = rtc.year - 1900; + tm.tm_isdst = 0; + + calendar = mktime(&tm); + if(timeptr != NULL) + *timeptr = calendar; + return calendar; +} diff --git a/src/libc/time/timeutil.h b/src/libc/time/timeutil.h new file mode 100644 index 0000000..5fdfc6f --- /dev/null +++ b/src/libc/time/timeutil.h @@ -0,0 +1,30 @@ +#ifndef __TIMEUTIL_H__ +# define __TIMEUTIL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/* Check whether a given year is a leap year. */ +static inline bool isleap(int yr) +{ + if(!(yr % 400)) return true; + if(!(yr % 100)) return false; + return !(yr & 3); +} + +/* Number of days for the given month (0..11) and year. */ +static inline int daysin(int month, int yr) +{ + static char count[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + return count[month] + (month == 1 && isleap(yr)); +} + +#ifdef __cplusplus +} +#endif + +#endif /*__TIMEUTIL_H__*/