aboutsummaryrefslogtreecommitdiff
path: root/libc/time.c
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/time.c
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/time.c')
-rw-r--r--libc/time.c142
1 files changed, 142 insertions, 0 deletions
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;
+}