aboutsummaryrefslogtreecommitdiff
path: root/libc
diff options
context:
space:
mode:
authorAlistair Popple <alistair@popple.id.au>2014-08-14 18:47:49 +1000
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>2014-08-15 13:03:08 +1000
commit39e53e483b186122db12712aca5dfb666e819729 (patch)
tree71d2402f8020b939c16151d5f29fd8823a825d3f /libc
parentc0515b6361b03d18cffcbc358d0f6109cd684031 (diff)
downloadskiboot-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.inc3
-rw-r--r--libc/include/time.h4
-rw-r--r--libc/test/Makefile.check22
-rw-r--r--libc/test/run-time.c82
-rw-r--r--libc/time.c142
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;
+}