time: add support for C99 <time.h> (DONE)

This commit is contained in:
Lephenixnoir 2021-11-29 17:10:54 +01:00
parent e71f9867e2
commit 92ccd8b1db
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
13 changed files with 365 additions and 25 deletions

View file

@ -192,7 +192,15 @@ set(SOURCES
src/libc/string/strstr.c src/libc/string/strstr.c
src/libc/string/strstr_base.c src/libc/string/strstr_base.c
src/libc/string/strtok.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 # Silence extended warnings on Grisu2b code
set_source_files_properties(3rdparty/grisu2b_59_56/grisu2b_59_56.c PROPERTIES set_source_files_properties(3rdparty/grisu2b_59_56/grisu2b_59_56.c PROPERTIES
@ -239,9 +247,13 @@ endif()
if(gint IN_LIST TARGET_FOLDERS) if(gint IN_LIST TARGET_FOLDERS)
list(APPEND SOURCES list(APPEND SOURCES
# stdlib
src/libc/stdlib/target/gint/free.c src/libc/stdlib/target/gint/free.c
src/libc/stdlib/target/gint/malloc.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() endif()
if(casiowin-fx IN_LIST TARGET_FOLDERS) if(casiowin-fx IN_LIST TARGET_FOLDERS)

26
STATUS
View file

@ -157,16 +157,16 @@ DONE: Function/symbol/macro is defined, builds, links, and is tested
7.22 <tgmath.h> => GCC 7.22 <tgmath.h> => GCC
7.23 <time.h> 7.23 <time.h>
! 7.23.1 Components of time: TODO 7.23.1 Components of time: DONE
! 7.23.2.1 clock: TODO 7.23.2.1 clock: DONE
! 7.23.2.2 difftime: TODO 7.23.2.2 difftime: DONE
! 7.23.2.3 mktime: TODO 7.23.2.3 mktime: DONE (DST flag ignored)
! 7.23.2.4 time: TODO 7.23.2.4 time: DONE
! 7.23.3.1 asctime: TODO 7.23.3.1 asctime: DONE
! 7.23.3.2 ctime: TODO 7.23.3.2 ctime: DONE
! 7.23.3.3 gmtime: TODO 7.23.3.3 gmtime: DONE
! 7.23.3.4 localtime: TODO 7.23.3.4 localtime: DONE (No timezones; same as gmtime)
! 7.23.3.5 strftime: TODO 7.23.3.5 strftime: DONE (No %g/%G/%U/%V/%W; timezones %z/%Z empty)
7.24 <wchar.h> 7.24 <wchar.h>
TODO (not a priority) 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 -> Fix the "TODO: locale: ..." messages wherever assumptions on the locale are
made in the code made in the code
-> Properly implement strcoll() and strxfrm() -> Properly implement strcoll() and strxfrm()
-> Add support in strftime()
# Supporting text and binary files (newline translation) # 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). 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. I really don't want to do that. Use multi-byte functions with UTF-8.
# Supporting timezones
-> Update localtime()
-> Add some timezone API

View file

@ -5,28 +5,65 @@
extern "C" { extern "C" {
#endif #endif
/* Number of ticks per second in a clock_t value. */ #include <stddef.h>
#include <stdint.h>
/* 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 #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; typedef uint64_t clock_t;
/* Type used to represent a number of seconds since Epoch. */ /* Represent a number of seconds since 1970-01-01 00:00:00 +0000 (UTC). */
typedef uint64_t time_t; typedef int64_t time_t;
/* Broken-down time. */ /* Broken-down time. */
struct tm { struct tm {
int tm_sec; int tm_sec; /* Seconds (0..60) */
int tm_min; int tm_min; /* Minutes (0..59) */
int tm_hour; int tm_hour; /* Hours (0..23) */
int tm_mday; int tm_mday; /* Day of month (1..31) */
int tm_mon; int tm_mon; /* Month (0..11) */
int tm_year; int tm_year; /* Years since 1900 */
int tm_wday; int tm_wday; /* Day of week, starting Sunday (0..6) */
int tm_yday; int tm_yday; /* Day of year (0..365) */
int tm_isdst; 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 #ifdef __cplusplus
} }
#endif #endif

21
src/libc/time/asctime.c Normal file
View file

@ -0,0 +1,21 @@
#include <time.h>
#include <stdio.h>
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;
}

6
src/libc/time/ctime.c Normal file
View file

@ -0,0 +1,6 @@
#include <time.h>
char *ctime(const time_t *time)
{
return asctime(localtime(time));
}

6
src/libc/time/difftime.c Normal file
View file

@ -0,0 +1,6 @@
#include <time.h>
double difftime(time_t t1, time_t t0)
{
return (double)(t1 - t0);
}

20
src/libc/time/gmtime.c Normal file
View file

@ -0,0 +1,20 @@
#include <time.h>
#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;
}

View file

@ -0,0 +1,7 @@
#include <time.h>
/* TODO: localtime: No timezone specification is supported */
struct tm *localtime(const time_t *time)
{
return gmtime(time);
}

58
src/libc/time/mktime.c Normal file
View file

@ -0,0 +1,58 @@
#include <time.h>
#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;
}

94
src/libc/time/strftime.c Normal file
View file

@ -0,0 +1,94 @@
#include <time.h>
#include <stdio.h>
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;
}

View file

@ -0,0 +1,20 @@
#include <time.h>
#include <gint/rtc.h>
#include <gint/defs/attributes.h>
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;
}

View file

@ -0,0 +1,23 @@
#include <time.h>
#include <gint/rtc.h>
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;
}

30
src/libc/time/timeutil.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef __TIMEUTIL_H__
# define __TIMEUTIL_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <time.h>
#include <stdbool.h>
/* 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__*/