summaryrefslogtreecommitdiff
path: root/lib/libc/time
diff options
context:
space:
mode:
authorKacper <kacper@mail.openlinux.dev>2025-12-07 20:10:31 +0100
committerKacper <kacper@mail.openlinux.dev>2025-12-07 20:10:31 +0100
commitfc00c656c96528112d05cf0edf8631bd5eaea446 (patch)
treea6e0e6c588191a8bd1c64afc3b7a258e3e66c236 /lib/libc/time
Add build system scaffolding and libc headers
Diffstat (limited to 'lib/libc/time')
-rw-r--r--lib/libc/time/asctime.c19
-rw-r--r--lib/libc/time/clock.c16
-rw-r--r--lib/libc/time/clock_getcpuclockid.c18
-rw-r--r--lib/libc/time/clock_getres.c7
-rw-r--r--lib/libc/time/clock_nanosleep.c8
-rw-r--r--lib/libc/time/ctime.c6
-rw-r--r--lib/libc/time/difftime.c6
-rw-r--r--lib/libc/time/gmtime_r.c42
-rw-r--r--lib/libc/time/localtime.c7
-rw-r--r--lib/libc/time/localtime_r.c7
-rw-r--r--lib/libc/time/nanosleep.c7
-rw-r--r--lib/libc/time/strftime.c562
-rw-r--r--lib/libc/time/time.c20
-rw-r--r--lib/libc/time/tzset.c58
-rw-r--r--lib/libc/time/utimes.c25
15 files changed, 808 insertions, 0 deletions
diff --git a/lib/libc/time/asctime.c b/lib/libc/time/asctime.c
new file mode 100644
index 00000000..54729a77
--- /dev/null
+++ b/lib/libc/time/asctime.c
@@ -0,0 +1,19 @@
+#include <time.h>
+#include <stdio.h>
+
+char *asctime(const struct tm *timeptr)
+{
+ static char wday_name[7][3] = { "Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat" };
+ static char mon_name[12][3] = { "Jan", "Feb", "Mar", "Apr",
+ "May", "Jun", "Jul", "Aug",
+ "Sep", "Oct", "Nov", "Dec" };
+ static char result[26];
+
+ snprintf(result, sizeof(result), "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n",
+ wday_name[timeptr->tm_wday], mon_name[timeptr->tm_mon],
+ timeptr->tm_mday, timeptr->tm_hour, timeptr->tm_min,
+ timeptr->tm_sec, 1900 + timeptr->tm_year);
+
+ return result;
+}
diff --git a/lib/libc/time/clock.c b/lib/libc/time/clock.c
new file mode 100644
index 00000000..ec2e6084
--- /dev/null
+++ b/lib/libc/time/clock.c
@@ -0,0 +1,16 @@
+#include <time.h>
+#include <limits.h>
+
+clock_t clock(void)
+{
+ struct timespec ts;
+
+ if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts))
+ return -1;
+
+ if (ts.tv_sec > LONG_MAX / 1000000 ||
+ ts.tv_nsec / 1000 > LONG_MAX - 1000000 * ts.tv_sec)
+ return -1;
+
+ return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
+}
diff --git a/lib/libc/time/clock_getcpuclockid.c b/lib/libc/time/clock_getcpuclockid.c
new file mode 100644
index 00000000..5bb53221
--- /dev/null
+++ b/lib/libc/time/clock_getcpuclockid.c
@@ -0,0 +1,18 @@
+#include <time.h>
+#include <syscall.h>
+
+int clock_getcpuclockid(pid_t pid, clockid_t *clock_id)
+{
+ int ret;
+ clockid_t id;
+ struct timespec ts;
+
+ id = (-pid - 1) * 8U + 2;
+ ret = syscall(clock_getres, id, &ts);
+
+ if (ret >= 0) {
+ *clock_id = id;
+ }
+
+ return ret;
+}
diff --git a/lib/libc/time/clock_getres.c b/lib/libc/time/clock_getres.c
new file mode 100644
index 00000000..5af37e8d
--- /dev/null
+++ b/lib/libc/time/clock_getres.c
@@ -0,0 +1,7 @@
+#include <time.h>
+#include <syscall.h>
+
+int clock_getres(clockid_t clock_id, struct timespec *res)
+{
+ return syscall(clock_getres, clock_id, res);
+}
diff --git a/lib/libc/time/clock_nanosleep.c b/lib/libc/time/clock_nanosleep.c
new file mode 100644
index 00000000..63190d06
--- /dev/null
+++ b/lib/libc/time/clock_nanosleep.c
@@ -0,0 +1,8 @@
+#include <time.h>
+#include <syscall.h>
+
+int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *rqtp,
+ struct timespec *rmtp)
+{
+ return __syscall(nanosleep, clock_id, flags, rqtp, rmtp) * -1;
+}
diff --git a/lib/libc/time/ctime.c b/lib/libc/time/ctime.c
new file mode 100644
index 00000000..bddd30cf
--- /dev/null
+++ b/lib/libc/time/ctime.c
@@ -0,0 +1,6 @@
+#include <time.h>
+
+char *ctime(const time_t *clock)
+{
+ return asctime(localtime(clock));
+}
diff --git a/lib/libc/time/difftime.c b/lib/libc/time/difftime.c
new file mode 100644
index 00000000..e7b567c1
--- /dev/null
+++ b/lib/libc/time/difftime.c
@@ -0,0 +1,6 @@
+#include <time.h>
+
+double difftime(time_t time1, time_t time0)
+{
+ return time1 - time0;
+}
diff --git a/lib/libc/time/gmtime_r.c b/lib/libc/time/gmtime_r.c
new file mode 100644
index 00000000..8d441549
--- /dev/null
+++ b/lib/libc/time/gmtime_r.c
@@ -0,0 +1,42 @@
+#include <time.h>
+
+struct tm *gmtime_r(const time_t *timer, struct tm *result)
+{
+ time_t t = *timer;
+ int days, rem;
+ int year, month;
+
+ rem = t % 86400;
+ days = t / 86400;
+ if (rem < 0) {
+ rem += 86400;
+ days--;
+ }
+
+ result->tm_hour = rem / 3600;
+ rem %= 3600;
+ result->tm_min = rem / 60;
+ result->tm_sec = rem % 60;
+ result->tm_isdst = 0;
+
+ result->tm_wday = (4 + days) % 7;
+ if (result->tm_wday < 0)
+ result->tm_wday += 7;
+
+ long z = days + 719468;
+ long era = (z >= 0 ? z : z - 146096) / 146097;
+ long day_of_era = z - era * 146097;
+ long yoe = (day_of_era - day_of_era / 1460 + day_of_era / 36524 -
+ day_of_era / 146096) /
+ 365;
+ year = (int)(yoe + era * 400);
+ long doy = day_of_era - (365 * yoe + yoe / 4 - yoe / 100);
+ result->tm_yday = (int)doy;
+
+ int mp = (5 * doy + 2) / 153;
+ result->tm_mday = (int)(doy - (153 * mp + 2) / 5 + 1);
+ result->tm_mon = (mp + 2) % 12;
+ result->tm_year = year - 1900 + (mp / 10);
+
+ return result;
+}
diff --git a/lib/libc/time/localtime.c b/lib/libc/time/localtime.c
new file mode 100644
index 00000000..44029920
--- /dev/null
+++ b/lib/libc/time/localtime.c
@@ -0,0 +1,7 @@
+#include <time.h>
+
+struct tm *localtime(const time_t *timer)
+{
+ static struct tm result;
+ return localtime_r(timer, &result);
+}
diff --git a/lib/libc/time/localtime_r.c b/lib/libc/time/localtime_r.c
new file mode 100644
index 00000000..1fb620a4
--- /dev/null
+++ b/lib/libc/time/localtime_r.c
@@ -0,0 +1,7 @@
+#include <time.h>
+
+struct tm *localtime_r(const time_t *restrict timer, struct tm *restrict result)
+{
+ time_t local = *timer - timezone;
+ return gmtime_r(&local, result);
+}
diff --git a/lib/libc/time/nanosleep.c b/lib/libc/time/nanosleep.c
new file mode 100644
index 00000000..f5c5f7c9
--- /dev/null
+++ b/lib/libc/time/nanosleep.c
@@ -0,0 +1,7 @@
+#include <time.h>
+#include <syscall.h>
+
+int nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
+{
+ return syscall(nanosleep, rqtp, rmtp);
+}
diff --git a/lib/libc/time/strftime.c b/lib/libc/time/strftime.c
new file mode 100644
index 00000000..d359140b
--- /dev/null
+++ b/lib/libc/time/strftime.c
@@ -0,0 +1,562 @@
+#include <time.h>
+#include <libc.h>
+#include <string.h>
+
+static size_t append_string(char *restrict *s, size_t *remaining,
+ const char *str)
+{
+ size_t len = strlen(str);
+ if (len >= *remaining) {
+ return 0;
+ }
+ strcpy(*s, str);
+ *s += len;
+ *remaining -= len;
+ return len;
+}
+
+static size_t append_char(char *restrict *s, size_t *remaining, char c)
+{
+ if (*remaining <= 1) {
+ return 0;
+ }
+ **s = c;
+ (*s)++;
+ (*remaining)--;
+ return 1;
+}
+
+static size_t format_int(char *restrict *s, size_t *remaining, int value,
+ int width, char pad, int show_sign)
+{
+ char buffer[32];
+ char *ptr = buffer + sizeof(buffer) - 1;
+ *ptr = '\0';
+
+ int negative = 0;
+ if (value < 0) {
+ negative = 1;
+ value = -value;
+ }
+
+ do {
+ *--ptr = '0' + (value % 10);
+ value /= 10;
+ } while (value > 0);
+
+ if (negative) {
+ *--ptr = '-';
+ } else if (show_sign && value >= 0) {
+ *--ptr = '+';
+ }
+
+ int len = (buffer + sizeof(buffer) - 1) - ptr;
+
+ while (len < width) {
+ *--ptr = pad;
+ len++;
+ }
+
+ return append_string(s, remaining, ptr);
+}
+
+static const char *weekday_abbr[] = { "Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat" };
+static const char *weekday_full[] = { "Sunday", "Monday", "Tuesday",
+ "Wednesday", "Thursday", "Friday",
+ "Saturday" };
+
+static const char *month_abbr[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+static const char *month_full[] = { "January", "February", "March",
+ "April", "May", "June",
+ "July", "August", "September",
+ "October", "November", "December" };
+
+static int day_of_year(const struct tm *tm)
+{
+ static const int days_to_month[] = { 0, 31, 59, 90, 120, 151,
+ 181, 212, 243, 273, 304, 334 };
+ int days = days_to_month[tm->tm_mon] + tm->tm_mday;
+
+ if (tm->tm_mon > 1) {
+ int year = tm->tm_year + 1900;
+ if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
+ days++;
+ }
+ }
+ return days;
+}
+
+static int iso_week_number(const struct tm *tm, int *week_year)
+{
+ int year = tm->tm_year + 1900;
+ int yday = tm->tm_yday + 1;
+ int wday;
+
+ int jan4_wday = (4 + year + (year - 1) / 4 - (year - 1) / 100 +
+ (year - 1) / 400) %
+ 7;
+ if (jan4_wday == 0)
+ jan4_wday = 7;
+
+ int week1_start = 4 - jan4_wday + 1;
+
+ int week = (yday - week1_start + 7) / 7;
+
+ if (week < 1) {
+ *week_year = year - 1;
+
+ int prev_jan4_wday = (jan4_wday - 1 + 7) % 7;
+ if (prev_jan4_wday == 0)
+ prev_jan4_wday = 7;
+ int prev_week1_start = 4 - prev_jan4_wday + 1;
+ int prev_year_days = 365;
+ if (((year - 1) % 4 == 0 && (year - 1) % 100 != 0) ||
+ ((year - 1) % 400 == 0)) {
+ prev_year_days = 366;
+ }
+ week = (prev_year_days - prev_week1_start + 8) / 7;
+ } else {
+ *week_year = year;
+
+ int year_days = 365;
+ if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
+ year_days = 366;
+ }
+ if (week1_start + (week - 1) * 7 > year_days - 3) {
+ if (yday > year_days - (7 - jan4_wday)) {
+ *week_year = year + 1;
+ week = 1;
+ }
+ }
+ }
+
+ return week;
+}
+
+size_t strftime(char *restrict s, size_t maxsize, const char *restrict format,
+ const struct tm *restrict timeptr)
+{
+ if (maxsize == 0)
+ return 0;
+
+ char *orig_s = s;
+ size_t remaining = maxsize - 1;
+ const char *ptr = format;
+
+ while (*ptr && remaining > 0) {
+ if (*ptr != '%') {
+ if (!append_char(&s, &remaining, *ptr)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ ptr++;
+ continue;
+ }
+
+ ptr++;
+ if (!*ptr)
+ break;
+
+ char pad_char = ' ';
+ int show_sign = 0;
+ int min_width = 0;
+
+ if (*ptr == '0') {
+ pad_char = '0';
+ ptr++;
+ } else if (*ptr == '+') {
+ pad_char = '0';
+ show_sign = 1;
+ ptr++;
+ }
+
+ while (*ptr >= '0' && *ptr <= '9') {
+ min_width = min_width * 10 + (*ptr - '0');
+ ptr++;
+ }
+
+ if (*ptr == 'E' || *ptr == 'O') {
+ ptr++;
+ }
+
+ switch (*ptr) {
+ case 'a':
+ if (!append_string(&s, &remaining,
+ weekday_abbr[timeptr->tm_wday])) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'A':
+ if (!append_string(&s, &remaining,
+ weekday_full[timeptr->tm_wday])) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'b':
+ case 'h':
+ if (!append_string(&s, &remaining,
+ month_abbr[timeptr->tm_mon])) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'B':
+ if (!append_string(&s, &remaining,
+ month_full[timeptr->tm_mon])) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'c':
+ if (!append_string(&s, &remaining,
+ weekday_abbr[timeptr->tm_wday]) ||
+ !append_char(&s, &remaining, ' ') ||
+ !append_string(&s, &remaining,
+ month_abbr[timeptr->tm_mon]) ||
+ !append_char(&s, &remaining, ' ') ||
+ !format_int(&s, &remaining, timeptr->tm_mday, 2,
+ ' ', 0) ||
+ !append_char(&s, &remaining, ' ') ||
+ !format_int(&s, &remaining, timeptr->tm_hour, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_min, 2, '0',
+ 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_sec, 2, '0',
+ 0) ||
+ !append_char(&s, &remaining, ' ') ||
+ !format_int(&s, &remaining, timeptr->tm_year + 1900,
+ 4, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'C':
+ if (!format_int(&s, &remaining,
+ (timeptr->tm_year + 1900) / 100,
+ min_width ? min_width : 2, pad_char,
+ show_sign)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'd':
+ if (!format_int(&s, &remaining, timeptr->tm_mday, 2,
+ '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'D':
+ if (!format_int(&s, &remaining, timeptr->tm_mon + 1, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, '/') ||
+ !format_int(&s, &remaining, timeptr->tm_mday, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, '/') ||
+ !format_int(&s, &remaining, timeptr->tm_year % 100,
+ 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'e':
+ if (!format_int(&s, &remaining, timeptr->tm_mday, 2,
+ ' ', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'F': {
+ int width = min_width ? min_width - 6 : 4;
+ if (width < 4)
+ width = 4;
+ if (!format_int(&s, &remaining, timeptr->tm_year + 1900,
+ width, pad_char, show_sign) ||
+ !append_char(&s, &remaining, '-') ||
+ !format_int(&s, &remaining, timeptr->tm_mon + 1, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, '-') ||
+ !format_int(&s, &remaining, timeptr->tm_mday, 2,
+ '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'g': {
+ int week_year;
+ iso_week_number(timeptr, &week_year);
+ if (!format_int(&s, &remaining, week_year % 100, 2, '0',
+ 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'G': {
+ int week_year;
+ iso_week_number(timeptr, &week_year);
+ if (!format_int(&s, &remaining, week_year,
+ min_width ? min_width : 4, pad_char,
+ show_sign)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'H':
+ if (!format_int(&s, &remaining, timeptr->tm_hour, 2,
+ '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'I': {
+ int hour12 = timeptr->tm_hour % 12;
+ if (hour12 == 0)
+ hour12 = 12;
+ if (!format_int(&s, &remaining, hour12, 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'j':
+ if (!format_int(&s, &remaining, timeptr->tm_yday + 1, 3,
+ '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'm':
+ if (!format_int(&s, &remaining, timeptr->tm_mon + 1, 2,
+ '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'M':
+ if (!format_int(&s, &remaining, timeptr->tm_min, 2, '0',
+ 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'n':
+ if (!append_char(&s, &remaining, '\n')) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'p':
+ if (!append_string(&s, &remaining,
+ timeptr->tm_hour < 12 ? "AM" :
+ "PM")) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'r': {
+ int hour12 = timeptr->tm_hour % 12;
+ if (hour12 == 0)
+ hour12 = 12;
+ if (!format_int(&s, &remaining, hour12, 2, '0', 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_min, 2, '0',
+ 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_sec, 2, '0',
+ 0) ||
+ !append_char(&s, &remaining, ' ') ||
+ !append_string(&s, &remaining,
+ timeptr->tm_hour < 12 ? "AM" :
+ "PM")) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'R':
+ if (!format_int(&s, &remaining, timeptr->tm_hour, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_min, 2, '0',
+ 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 's':
+
+ if (!append_char(&s, &remaining, '0')) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'S':
+ if (!format_int(&s, &remaining, timeptr->tm_sec, 2, '0',
+ 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 't':
+ if (!append_char(&s, &remaining, '\t')) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'T':
+ if (!format_int(&s, &remaining, timeptr->tm_hour, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_min, 2, '0',
+ 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_sec, 2, '0',
+ 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'u': {
+ int wday = timeptr->tm_wday;
+ if (wday == 0)
+ wday = 7;
+ if (!format_int(&s, &remaining, wday, 1, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'U': {
+ int week =
+ (timeptr->tm_yday + 7 - timeptr->tm_wday) / 7;
+ if (!format_int(&s, &remaining, week, 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'V': {
+ int week_year;
+ int week = iso_week_number(timeptr, &week_year);
+ if (!format_int(&s, &remaining, week, 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'w':
+ if (!format_int(&s, &remaining, timeptr->tm_wday, 1,
+ '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'W': {
+ int wday = timeptr->tm_wday;
+ if (wday == 0)
+ wday = 7;
+ int week = (timeptr->tm_yday + 7 - wday + 1) / 7;
+ if (!format_int(&s, &remaining, week, 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'x':
+ if (!format_int(&s, &remaining, timeptr->tm_mon + 1, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, '/') ||
+ !format_int(&s, &remaining, timeptr->tm_mday, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, '/') ||
+ !format_int(&s, &remaining, timeptr->tm_year % 100,
+ 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'X':
+ if (!format_int(&s, &remaining, timeptr->tm_hour, 2,
+ '0', 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_min, 2, '0',
+ 0) ||
+ !append_char(&s, &remaining, ':') ||
+ !format_int(&s, &remaining, timeptr->tm_sec, 2, '0',
+ 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'y':
+ if (!format_int(&s, &remaining, timeptr->tm_year % 100,
+ 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'Y':
+ if (!format_int(&s, &remaining, timeptr->tm_year + 1900,
+ min_width ? min_width : 4, pad_char,
+ show_sign)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ case 'z': {
+ if (timeptr->tm_isdst < 0) {
+ break;
+ }
+ long offset = timeptr->tm_gmtoff;
+ char sign = '+';
+ if (offset < 0) {
+ sign = '-';
+ offset = -offset;
+ }
+ int hours = offset / 3600;
+ int minutes = (offset % 3600) / 60;
+ if (!append_char(&s, &remaining, sign) ||
+ !format_int(&s, &remaining, hours, 2, '0', 0) ||
+ !format_int(&s, &remaining, minutes, 2, '0', 0)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ } break;
+ case 'Z':
+ if (timeptr->tm_zone) {
+ if (!append_string(&s, &remaining,
+ timeptr->tm_zone)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ }
+ break;
+ case '%':
+ if (!append_char(&s, &remaining, '%')) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ default:
+
+ if (!append_char(&s, &remaining, '%') ||
+ !append_char(&s, &remaining, *ptr)) {
+ *orig_s = '\0';
+ return 0;
+ }
+ break;
+ }
+ ptr++;
+ }
+
+ *s = '\0';
+ return s - orig_s;
+}
+
+weak size_t strftime_l(char *restrict s, size_t maxsize,
+ const char *restrict format,
+ const struct tm *restrict timeptr,
+ locale_t unused locale)
+{
+ return strftime(s, maxsize, format, timeptr);
+}
diff --git a/lib/libc/time/time.c b/lib/libc/time/time.c
new file mode 100644
index 00000000..134eed7b
--- /dev/null
+++ b/lib/libc/time/time.c
@@ -0,0 +1,20 @@
+#include <time.h>
+#include <asm/vdso.h>
+#include <syscall.h>
+
+time_t time(time_t *tloc)
+{
+ struct timespec ts;
+
+#if defined(__x86_64__)
+ if (__vdso_time)
+ return __vdso_time(tloc);
+#endif
+
+ clock_gettime(CLOCK_REALTIME, &ts);
+
+ if (tloc)
+ *tloc = ts.tv_sec;
+
+ return ts.tv_sec;
+}
diff --git a/lib/libc/time/tzset.c b/lib/libc/time/tzset.c
new file mode 100644
index 00000000..96f33702
--- /dev/null
+++ b/lib/libc/time/tzset.c
@@ -0,0 +1,58 @@
+#include <time.h>
+#include <ctype.h>
+
+int daylight = 0;
+long timezone = 0;
+char *tzname[2] = { "UTC", "UTC" };
+
+void tzset(void)
+{
+ const char *tz = NULL; // getenv("TZ");
+
+ if (tz == NULL || *tz == '\0') {
+ timezone = 0;
+ daylight = 0;
+ tzname[0] = tzname[1] = "UTC";
+ return;
+ }
+
+ const char *p = tz;
+ char sign = 0;
+ int hours = 0, mins = 0;
+
+ static char stdname[8];
+ int i = 0;
+ while (*p && !isdigit((unsigned char)*p) && *p != '+' && *p != '-' &&
+ i < 7) {
+ stdname[i++] = *p++;
+ }
+
+ stdname[i] = '\0';
+ if (stdname[0])
+ tzname[0] = stdname;
+ else
+ tzname[0] = "LCL";
+
+ if (*p == '+' || *p == '-')
+ sign = *p++;
+
+ while (isdigit((unsigned char)*p))
+ hours = hours * 10 + (*p++ - '0');
+
+ if (*p == ':') {
+ p++;
+ while (isdigit((unsigned char)*p))
+ mins = mins * 10 + (*p++ - '0');
+ }
+
+ int total = hours * 3600 + mins * 60;
+ if (sign == '+')
+ timezone = -total;
+ else if (sign == '-')
+ timezone = total;
+ else
+ timezone = 0;
+
+ daylight = 0;
+ tzname[1] = tzname[0];
+}
diff --git a/lib/libc/time/utimes.c b/lib/libc/time/utimes.c
new file mode 100644
index 00000000..d6448c76
--- /dev/null
+++ b/lib/libc/time/utimes.c
@@ -0,0 +1,25 @@
+#include <fcntl.h>
+#include <errno.h>
+#include <stddef.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+int utimes(const char *path, const struct timeval times[2])
+{
+ struct timespec ts[2];
+
+ if (times) {
+ if (times[0].tv_usec >= 1000000ULL ||
+ times[1].tv_usec >= 1000000ULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ts[0].tv_sec = times[0].tv_sec;
+ ts[0].tv_nsec = times[0].tv_usec * 1000;
+ ts[1].tv_sec = times[1].tv_sec;
+ ts[1].tv_nsec = times[1].tv_usec * 1000;
+ }
+
+ return utimensat(AT_FDCWD, path, times ? ts : NULL, 0);
+}