diff options
author | Alistair Popple <alistair@popple.id.au> | 2014-08-14 18:47:49 +1000 |
---|---|---|
committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2014-08-15 13:03:08 +1000 |
commit | 39e53e483b186122db12712aca5dfb666e819729 (patch) | |
tree | 71d2402f8020b939c16151d5f29fd8823a825d3f /libc | |
parent | c0515b6361b03d18cffcbc358d0f6109cd684031 (diff) | |
download | skiboot-39e53e483b186122db12712aca5dfb666e819729.zip skiboot-39e53e483b186122db12712aca5dfb666e819729.tar.gz skiboot-39e53e483b186122db12712aca5dfb666e819729.tar.bz2 |
libc: Add mktime and gmtime_r
Both the FSP RTC and the upcoming IPMI RTC implementation need to
manipulate time in various ways. Rather than re-implementing slightly
different versions of the calculations twice lets implement some
standard library functions (with tests) and use those.
This patch adds mktime and gmtime_r to the libc.
Signed-off-by: Alistair Popple <alistair@popple.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'libc')
-rw-r--r-- | libc/Makefile.inc | 3 | ||||
-rw-r--r-- | libc/include/time.h | 4 | ||||
-rw-r--r-- | libc/test/Makefile.check | 22 | ||||
-rw-r--r-- | libc/test/run-time.c | 82 | ||||
-rw-r--r-- | libc/time.c | 142 |
5 files changed, 250 insertions, 3 deletions
diff --git a/libc/Makefile.inc b/libc/Makefile.inc index 7503f09..66ce4ab 100644 --- a/libc/Makefile.inc +++ b/libc/Makefile.inc @@ -1,7 +1,7 @@ LIBCDIR = libc SUBDIRS += $(LIBCDIR) -LIBC = $(LIBCDIR)/built-in.o +LIBC = $(LIBCDIR)/built-in.o $(LIBCDIR)/time.o include $(SRC)/$(LIBCDIR)/string/Makefile.inc include $(SRC)/$(LIBCDIR)/ctype/Makefile.inc @@ -9,4 +9,3 @@ include $(SRC)/$(LIBCDIR)/stdlib/Makefile.inc include $(SRC)/$(LIBCDIR)/stdio/Makefile.inc $(LIBC): $(STRING) $(CTYPE) $(STDLIB) $(STDIO) - diff --git a/libc/include/time.h b/libc/include/time.h index 807023b..517f596 100644 --- a/libc/include/time.h +++ b/libc/include/time.h @@ -33,10 +33,12 @@ struct timespec { long tv_nsec; /* nanoseconds */ }; +struct tm *gmtime_r(const time_t *timep, struct tm *result); +time_t mktime(struct tm *tm); + /* Not implemented by libc but by hosting environment, however * this is where the prototype is expected */ int nanosleep(const struct timespec *req, struct timespec *rem); #endif /* _TIME_H */ - diff --git a/libc/test/Makefile.check b/libc/test/Makefile.check new file mode 100644 index 0000000..188b6b1 --- /dev/null +++ b/libc/test/Makefile.check @@ -0,0 +1,22 @@ +# -*-Makefile-*- +LIBC_TEST := libc/test/run-time + +check: $(LIBC_TEST:%=%-check) + +$(LIBC_TEST:%=%-check) : %-check: % + $(VALGRIND) $< + +$(LIBC_TEST) : % : %.c + $(HOSTCC) $(HOSTCFLAGS) -O0 -g -I include -I . -I libfdt -I libc/include -o $@ $< + +$(LIBC_TEST): % : %.d + +libc/test/%.d: libc/test/%.c + $(HOSTCC) $(HOSTCFLAGS) -I include -I . -I libfdt -I libc/include -M $< > $@ + +-include libc/test/*.d + +clean: libc-test-clean + +libc-test-clean: + $(RM) -f libc/test/*.[od] $(LIBC_TEST) diff --git a/libc/test/run-time.c b/libc/test/run-time.c new file mode 100644 index 0000000..95662f4 --- /dev/null +++ b/libc/test/run-time.c @@ -0,0 +1,82 @@ +#include "/usr/include/assert.h" +#include <stdio.h> +#include <time.h> + +#include "../time.c" + +#define MKTIME_TEST(Y,M,D,h,m,s,t) \ + tm.tm_year = Y; \ + tm.tm_mon = M; \ + tm.tm_mday = D; \ + tm.tm_hour = h; \ + tm.tm_min = m; \ + tm.tm_sec = s; \ + assert(mktime(&tm) == t); \ + assert(tm.tm_year == Y); \ + assert(tm.tm_mon == M); \ + assert(tm.tm_mday == D); \ + assert(tm.tm_hour == h); \ + assert(tm.tm_min == m); \ + assert(tm.tm_sec == s) + +#define GMTIME_TEST(Y,M,D,h,m,s,tv) \ + t = tv; \ + gmtime_r(&t, &tm); \ + assert(tm.tm_year == Y); \ + assert(tm.tm_mon == M); \ + assert(tm.tm_mday == D); \ + assert(tm.tm_hour == h); \ + assert(tm.tm_min == m); \ + assert(tm.tm_sec == s) + +#define TIME_TEST(Y,M,D,h,m,s,tv) \ + MKTIME_TEST(Y,M,D,h,m,s,tv); \ + GMTIME_TEST(Y,M,D,h,m,s,tv) + +int main(void) +{ + struct tm tm; + time_t t = 0; + uint32_t time; + + TIME_TEST(1970, 0, 1, 0, 0, 0, 0); + TIME_TEST(1971, 0, 1, 0, 0, 0, 365*SECS_PER_DAY); + TIME_TEST(1972, 0, 1, 0, 0, 0, 2*365*SECS_PER_DAY); + TIME_TEST(1972, 11, 31, 0, 0, 0, 3*365*SECS_PER_DAY); + TIME_TEST(1973, 0, 1, 0, 0, 0, (3*365+1)*SECS_PER_DAY); + TIME_TEST(2000, 11, 31, 0, 0, 0, 978220800); + TIME_TEST(2001, 0, 1, 0, 0, 0, 978307200); + TIME_TEST(2003, 11, 31, 0, 0, 0, 1072828800); + TIME_TEST(2004, 0, 1, 0, 0, 0, 1072828800+SECS_PER_DAY); + TIME_TEST(2004, 11, 29, 0, 0, 0, 1072828800+364*SECS_PER_DAY); + TIME_TEST(2004, 11, 30, 0, 0, 0, 1072828800+365*SECS_PER_DAY); + TIME_TEST(2004, 11, 31, 0, 0, 0, 1072828800+366*SECS_PER_DAY); + TIME_TEST(2004, 11, 31, 23, 59, 59, 1072828800+367*SECS_PER_DAY-1); + TIME_TEST(2100, 11, 31, 0, 0, 0, 4133894400); + TIME_TEST(2101, 0, 1, 0, 0, 0, 4133980800); + + /* Test the normalisation functionality of mktime */ + tm.tm_year = 2000; + tm.tm_mon = 1; + tm.tm_mday = 10; + tm.tm_hour = 5; + tm.tm_min = 32; + tm.tm_sec = 105; + mktime(&tm); + assert(tm.tm_year == 2000); + assert(tm.tm_mon == 1); + assert(tm.tm_mday == 10); + assert(tm.tm_hour == 5); + assert(tm.tm_min == 33); + assert(tm.tm_sec == 45); + tm.tm_sec += 366*24*60*60; + mktime(&tm); + assert(tm.tm_year == 2001); + assert(tm.tm_mon == 1); + assert(tm.tm_mday == 10); + assert(tm.tm_hour == 5); + assert(tm.tm_min == 33); + assert(tm.tm_sec == 45); + + return 0; +} diff --git a/libc/time.c b/libc/time.c new file mode 100644 index 0000000..9be6e11 --- /dev/null +++ b/libc/time.c @@ -0,0 +1,142 @@ +#include <stdbool.h> +#include <time.h> + +/* + * Returns the number of leap years prior to the given year. + */ +static int leap_years(int year) +{ + return (year-1)/4 + (year-1)/400 - (year-1)/100; +} + +static int is_leap_year(int year) +{ + return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0); +} + +static int days_in_month(int month, int year) +{ + static int month_days[] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + + /* we may need to update this in the year 4000, pending a + * decision on whether or not it's a leap year */ + if (month == 1) + return is_leap_year(year) ? 29 : 28; + + return month_days[month]; +} + +static const int days_per_month[2][13] = + {{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}}; + +#define SECS_PER_MIN 60 +#define SECS_PER_HOUR (SECS_PER_MIN*60) +#define SECS_PER_DAY (24*SECS_PER_HOUR) +#define DAYS_PER_YEAR 365 +struct tm *gmtime_r(const time_t *timep, struct tm *result) +{ + int i; + int Y; + int M; + int D; + int h; + int m; + int s; + + D = *timep / SECS_PER_DAY; + s = *timep % SECS_PER_DAY; + m = s / 60; + h = m / 60; + m %= 60; + s %= 60; + + /* + * Work out the year. We subtract one day for every four years + * and every 400 years after 1969. However as leap years don't + * occur every 100 years we add one day back to counteract the + * the substraction for every 4 years. + */ + Y = (D - (1+D/365)/4 + (69+D/365)/100 - (369+D/365)/400)/365; + + /* + * Remember we're doing integer arithmetic here so + * leap_years(Y+1970) - leap_years(1970) != leap_years(Y) + */ + D = D - Y*365 - (leap_years(Y+1970) - leap_years(1970)) + 1; + Y += 1970; + + M = 0; + for (i = 0; i < 13; i++) + if (D <= days_per_month[is_leap_year(Y) ? 1 : 0][i]) { + M = i; + break; + } + + D -= days_per_month[is_leap_year(Y)][M-1]; + result->tm_year = Y; + result->tm_mon = M - 1; + result->tm_mday = D; + result->tm_hour = h; + result->tm_min = m; + result->tm_sec = s; + return result; +} + +time_t mktime(struct tm *tm) +{ + unsigned long year, month, mday, hour, minute, second, d; + static const unsigned long sec_in_400_years = + ((3903ul * 365) + (97 * 366)) * 24 * 60 * 60; + + second = tm->tm_sec; + minute = tm->tm_min; + hour = tm->tm_hour; + mday = tm->tm_mday; + month = tm->tm_mon; + year = tm->tm_year; + + /* There are the same number of seconds in any 400-year block; this + * limits the iterations in the loop below */ + year += 400 * (second / sec_in_400_years); + second = second % sec_in_400_years; + + if (second >= 60) { + minute += second / 60; + second = second % 60; + } + + if (minute >= 60) { + hour += minute / 60; + minute = minute % 60; + } + + if (hour >= 24) { + mday += hour / 24; + hour = hour % 24; + } + + for (d = days_in_month(month, year); mday > d; + d = days_in_month(month, year)) { + month++; + if (month > 12) { + month = 0; + year++; + } + mday -= d; + } + + tm->tm_year = year; + tm->tm_mon = month; + tm->tm_mday = mday; + tm->tm_hour = hour; + tm->tm_min = minute; + tm->tm_sec = second; + + d = mday; + d += days_per_month[is_leap_year(year)][month]; + d += (year-1970)*DAYS_PER_YEAR + leap_years(year) - leap_years(1970) - 1; + return d*SECS_PER_DAY + hour*SECS_PER_HOUR + minute*SECS_PER_MIN + second; +} |