feat(time): time/clock/mktime/strftime/nanosleep

This commit is contained in:
2026-09-05 22:33:02 -04:00
parent 3eff4c97fd
commit ed996ff4c4
14 changed files with 3037 additions and 0 deletions
+255
View File
@@ -0,0 +1,255 @@
#ifndef VLIBC_TIME_H
#define VLIBC_TIME_H
/*
* vlibc — <time.h>.
*
* Calendar time, clock ids, and interval timers. The ISO C functions
* (time, clock, timespec_get, difftime, asctime/ctime/mktime/gmtime/
* localtime, strftime) and the POSIX clock_* / timer_* / sleep family are
* thin, level-gated wrappers over the Linux time syscalls.
*
* Level 1 (onlyposix): time, clock, timespec_get, difftime,
* clock_gettime/settime/getres/getcpuclockid,
* clock_nanosleep, nanosleep, sleep, and the
* broken-down-time/formatting declarations
* (mktime, gmtime/localtime and _r forms, asctime,
* ctime, strftime, tzset).
* Level 2 (muslmimic): POSIX interval timers (timer_create/delete/
* settime/gettime/getoverrun) and the XSI
* conveniences (timegm, strftime_l, usleep,
* asctime_r, ctime_r, strptime, tzname/daylight/
* timezone).
*
* The scalar time types (time_t, clock_t, clockid_t, pid_t, timer_t and
* useconds_t) come from <sys/types.h>, which this header includes; struct
* timespec and struct tm are defined here. timer_t is the <sys/types.h>
* opaque pointer type (kernel timer ids are returned through it); struct
* sigevent and struct itimerspec stay forward-declared here — their kernel
* ABI mirrors are private to the timer implementation (see src/time/
* timer.c) and to consumers that define matching structs.
*
* The clock ids and TIMER_ABSTIME mirror the kernel ABI (x86_64): the
* CLOCK_* values are passed to SYS_clock_* and SYS_timer_* unchanged, and
* TIMER_ABSTIME is the timer_settime/clock_nanosleep flag bit.
*/
#include <vlibc/features.h>
#include <stddef.h>
#include <sys/types.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Base argument of timespec_get(); the only base defined by ISO C. */
#define TIME_UTC 1
/* Ticks per second reported by clock() (vlibc always reports 1 MHz). */
#define CLOCKS_PER_SEC 1000000
/* Clock ids (kernel ABI; SYS_clock_* argument). */
#define CLOCK_REALTIME 0 /* wall clock since the Epoch */
#define CLOCK_MONOTONIC 1 /* monotonic since boot */
#define CLOCK_PROCESS_CPUTIME_ID 2 /* CPU time of this process */
#define CLOCK_THREAD_CPUTIME_ID 3 /* CPU time of this thread */
/* Flag for absolute (vs relative) timer expiry. */
#define TIMER_ABSTIME 1
/*
* Elapsed time, seconds + nanoseconds. Kernel ABI layout (x86_64): the
* wrappers pass this straight to the clock/nanosleep/timer syscalls.
*/
struct timespec
{
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds (0..999999999) */
};
/*
* Broken-down civil time. tm_gmtoff and tm_zone are the XSI extensions
* (seconds east of UTC / timezone name) carried for glibc compatibility.
*/
struct tm
{
int tm_sec; /* seconds after the minute (0..60) */
int tm_min; /* minutes after the hour (0..59) */
int tm_hour; /* hours since midnight (0..23) */
int tm_mday; /* day of the month (1..31) */
int tm_mon; /* months since January (0..11) */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday (0..6) */
int tm_yday; /* days since January 1 (0..365) */
int tm_isdst; /* daylight-saving flag */
long tm_gmtoff; /* seconds east of UTC */
const char *tm_zone; /* timezone abbreviation */
};
/* Return the current wall-clock time in seconds; if t is not NULL store it
* there too, and return (time_t)-1 with errno set on failure. */
time_t
time(time_t *t);
/* Approximate processor time in CLOCKS_PER_SEC units since an arbitrary
* epoch, or (clock_t)-1 with errno set on failure. */
clock_t
clock(void);
/* Store the current time for base into ts and return base; return 0 if
* base is not TIME_UTC or the clock cannot be read. */
int
timespec_get(struct timespec *ts, int base);
/* Read the given clock into tp. Return 0 or -1 with errno set. */
int
clock_gettime(clockid_t clockid, struct timespec *tp);
/* Set the given clock from tp. Return 0 or -1 with errno set. */
int
clock_settime(clockid_t clockid, const struct timespec *tp);
/* Resolution of the given clock; store it in res when res is not NULL.
* Return 0 or -1 with errno set. */
int
clock_getres(clockid_t clockid, struct timespec *res);
/* Clock id measuring the CPU time of the given process (0 = calling
* process). Return 0, or -1 with errno set when the process is unknown. */
int
clock_getcpuclockid(pid_t pid, clockid_t *clock_id);
/* Sleep on the given clock. Without TIMER_ABSTIME request is relative and
* the sleep restarts from remain on signal interruption. */
int
clock_nanosleep(clockid_t clockid, int flags, const struct timespec *request,
struct timespec *remain);
/* Sleep request seconds; when interrupted and remain is not NULL the sleep
* restarts from the remaining time. Return 0 or -1 with errno set. */
int
nanosleep(const struct timespec *request, struct timespec *remain);
/* Sleep seconds (whole seconds; signals can shorten the sleep, which is
* then resumed). Return the unslept seconds, normally 0. */
unsigned int
sleep(unsigned int seconds);
/* Convert broken-down civil time back to calendar seconds. */
time_t
mktime(struct tm *tm);
/* Broken-down local time; localtime uses an internal static buffer.
* Return NULL on error. */
struct tm *
localtime(const time_t *timer);
/* Broken-down local time into buf. Return buf or NULL on error. */
struct tm *
localtime_r(const time_t *timer, struct tm *buf);
/* Broken-down UTC; gmtime uses an internal static buffer. */
struct tm *
gmtime(const time_t *timer);
/* Broken-down UTC into buf. Return buf or NULL on error. */
struct tm *
gmtime_r(const time_t *timer, struct tm *buf);
/* Fixed-format rendering of broken-down time ("Sun Sep 16 01:03:52 1973"). */
char *
asctime(const struct tm *tm);
/* asctime(localtime(timer)); fixed-format wall-clock rendering. */
char *
ctime(const time_t *timer);
/* Format broken-down time per format into s (at most maxsize bytes);
* return the bytes written (0 when the buffer was too small). */
size_t
strftime(char *restrict s, size_t maxsize, const char *restrict format,
const struct tm *restrict timeptr);
/* Seconds between two calendar times (b subtracted from a). */
double
difftime(time_t a, time_t b);
/* Establish the local timezone from TZ / the system default. */
void
tzset(void);
#if VLIBC_LEVEL_GE(2)
/* Level 2 (muslmimic): POSIX interval timers and XSI conveniences. */
/* Opaque kernel-ABI structures; consumers define layout-compatible structs
* (see src/time/timer.c). */
struct sigevent;
struct itimerspec;
/* Create a per-process interval timer on clockid; the timer id is stored
* through timerid. Return 0 or -1 with errno set. */
int
timer_create(clockid_t clockid, const struct sigevent *restrict evp, timer_t *restrict timerid);
/* Destroy the given timer. Return 0 or -1 with errno set. */
int
timer_delete(timer_t timerid);
/* Arm/disarm the timer; when old_value is not NULL the previous setting is
* stored there. Return 0 or -1 with errno set. */
int
timer_settime(timer_t timerid, int flags, const struct itimerspec *restrict new_value,
struct itimerspec *restrict old_value);
/* Remaining time until the timer expires (and its interval). Return 0 or
* -1 with errno set. */
int
timer_gettime(timer_t timerid, struct itimerspec *curr_value);
/* Number of timer expirations between the most recent signal delivery and
* the one before it; -1 with errno set when there was no pending delivery. */
int
timer_getoverrun(timer_t timerid);
/* mktime in UTC rather than local time. */
time_t
timegm(struct tm *tm);
/* strftime with an explicit locale object (locale_t is not yet defined;
* the locale argument is an opaque pointer). */
size_t
strftime_l(char *restrict s, size_t maxsize, const char *restrict format,
const struct tm *restrict timeptr, void *loc);
/* Microsecond sleep (whole microseconds; signals resume the sleep).
* Return 0 or the unslept microseconds with errno set. */
useconds_t
usleep(useconds_t usec);
/* asctime into a caller-provided buffer of at least 26 bytes. */
char *
asctime_r(const struct tm *tm, char *buf);
/* ctime into a caller-provided buffer of at least 26 bytes. */
char *
ctime_r(const time_t *timer, char *buf);
/* Parse format from s into broken-down time; return the first character
* not consumed, or NULL when the format did not match. */
char *
strptime(const char *restrict s, const char *restrict format, struct tm *restrict tm);
/* Timezone state maintained by tzset(). */
extern char *tzname[2];
extern int daylight;
extern long timezone;
#endif /* VLIBC_LEVEL_GE(2) */
#ifdef __cplusplus
}
#endif
#endif /* VLIBC_TIME_H */
+112
View File
@@ -0,0 +1,112 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <time.h>
#include "../internal/syscall.h"
/*
* struct tms layout for SYS_times: four process-CPU clock-tick counters,
* in kernel order (x86_64: four longs; the kernel ABI names it __kernel_tms).
* It is private to this file; <sys/times.h> (a later todo) will own the
* public definition.
*/
struct tms
{
clock_t tms_utime; /* user CPU time */
clock_t tms_stime; /* system CPU time */
clock_t tms_cutime; /* user CPU time of waited-for children */
clock_t tms_cstime; /* system CPU time of waited-for children */
};
/*
* clock: the calling process's CPU time in CLOCKS_PER_SEC units.
*
* SYS_times reports CPU time in USER_HZ ticks (100 per second on x86_64),
* so the tick counts are scaled by CLOCKS_PER_SEC / 100 == 10000 to the
* 1 MHz clock() unit. On failure SYS_times returns -1 and clock() reports
* (clock_t)-1 with errno set.
*/
clock_t
clock(void)
{
struct tms t;
if (syscall_ret(__syscall1(SYS_times, (long)&t)) < 0)
{
return (clock_t)-1;
}
return (clock_t)((t.tms_utime + t.tms_stime) * (CLOCKS_PER_SEC / 100));
}
/*
* clock_gettime/clock_settime/clock_getres: raw kernel clock reads and
* writes over SYS_clock_gettime(228)/SYS_clock_settime(227)/
* SYS_clock_getres(229). The clock ids (CLOCK_REALTIME and friends) are
* kernel ABI values and pass through unchanged; the struct timespec
* arguments are the kernel's own layout.
*/
int
clock_gettime(clockid_t clockid, struct timespec *tp)
{
return syscall_ret(__syscall2(SYS_clock_gettime, (long)clockid, (long)tp));
}
int
clock_settime(clockid_t clockid, const struct timespec *tp)
{
return syscall_ret(__syscall2(SYS_clock_settime, (long)clockid, (long)tp));
}
int
clock_getres(clockid_t clockid, struct timespec *res)
{
return syscall_ret(__syscall2(SYS_clock_getres, (long)clockid, (long)res));
}
/*
* clock_getcpuclockid: the clock id that measures a given process's CPU
* time.
*
* The calling process (pid 0 or our own pid) is always reported as
* CLOCK_PROCESS_CPUTIME_ID. Any other pid is encoded with the kernel's
* CPUCLOCK_PID scheme — (~pid << 3) | 2 — and validated by probing that
* clock with SYS_clock_getres, which the kernel answers with ESRCH when no
* such process exists. That probe failing is the only errno write this
* file performs on its own; every other path leaves errno to syscall_ret.
*/
int
clock_getcpuclockid(pid_t pid, clockid_t *clock_id)
{
pid_t self = (pid_t)syscall_ret(__syscall0(SYS_getpid));
clockid_t id;
if (pid == 0)
{
pid = self;
}
if (pid == self)
{
*clock_id = CLOCK_PROCESS_CPUTIME_ID;
return 0;
}
/* Linux CPUCLOCK_PID encoding of a process clock id. */
id = (clockid_t)((~(unsigned)pid << 3) | 2u);
/* Probe the encoded id; the kernel rejects unknown pids with ESRCH. */
{
struct timespec res;
if (syscall_ret(__syscall2(SYS_clock_getres, (long)id, (long)&res)) < 0)
{
errno = ESRCH;
return -1;
}
}
*clock_id = id;
return 0;
}
+135
View File
@@ -0,0 +1,135 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
/*
* asctime/ctime: the fixed-format "Www Mmm dd hh:mm:ss yyyy\n" rendering
* (26 bytes including the terminating NUL). Both functions are thin layers
* over a single buffer writer: asctime renders a broken-down time directly,
* ctime renders localtime(t). Per POSIX the conversion fails (NULL) when
* the calendar year lies outside [1000, 9999], which is also what keeps the
* fixed 26-byte layout exact.
*/
static const char *const wd_abbr[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *const mon_abbr[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
/*
* Render tm into buf (26 bytes: 24 + '\n' + '\0'). Returns buf, or NULL
* when the year is outside the representable range (the buffer is then
* left untouched, matching the POSIX contract).
*/
static char *
asctime_write(const struct tm *tm, char *buf)
{
int year = tm->tm_year + 1900;
int wd = tm->tm_wday;
int mo = tm->tm_mon;
int mday = tm->tm_mday;
const char *s;
char *p = buf;
if (year < 1000 || year > 9999)
{
return NULL;
}
if (wd < 0)
{
wd = 7 - (-wd) % 7;
if (wd == 7)
{
wd = 0;
}
}
else
{
wd %= 7;
}
if (mo < 0)
{
mo = 12 - (-mo) % 12;
if (mo == 12)
{
mo = 0;
}
}
else
{
mo %= 12;
}
s = wd_abbr[wd];
while (*s != '\0')
{
*p++ = *s++;
}
*p++ = ' ';
s = mon_abbr[mo];
while (*s != '\0')
{
*p++ = *s++;
}
*p++ = ' ';
if (mday < 10)
{
*p++ = ' ';
}
*p++ = (char)('0' + (mday / 10) % 10);
*p++ = (char)('0' + mday % 10);
*p++ = ' ';
*p++ = (char)('0' + (tm->tm_hour / 10) % 10);
*p++ = (char)('0' + tm->tm_hour % 10);
*p++ = ':';
*p++ = (char)('0' + (tm->tm_min / 10) % 10);
*p++ = (char)('0' + tm->tm_min % 10);
*p++ = ':';
*p++ = (char)('0' + (tm->tm_sec / 10) % 10);
*p++ = (char)('0' + tm->tm_sec % 10);
*p++ = ' ';
*p++ = (char)('0' + (year / 1000) % 10);
*p++ = (char)('0' + (year / 100) % 10);
*p++ = (char)('0' + (year / 10) % 10);
*p++ = (char)('0' + year % 10);
*p++ = '\n';
*p = '\0';
return buf;
}
char *
asctime(const struct tm *tm) // NOLINT(concurrency-mt-unsafe)
{
static char asctime_buf[26];
return asctime_write(tm, asctime_buf);
}
char *
ctime(const time_t *timer)
{
return asctime(localtime(timer));
}
#if VLIBC_LEVEL_GE(2)
char *
asctime_r(const struct tm *tm, char *buf)
{
return asctime_write(tm, buf);
}
char *
ctime_r(const time_t *timer, char *buf)
{
struct tm tmv;
if (localtime_r(timer, &tmv) == NULL)
{
return NULL;
}
return asctime_write(&tmv, buf);
}
#endif /* VLIBC_LEVEL_GE(2) */
+188
View File
@@ -0,0 +1,188 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <limits.h>
#include <time.h>
#include "time_impl.h"
/*
* Civil <-> serial conversions (mktime/timegm/__fill_tm).
*
* days_from_civil/civil_from_days are Howard Hinnant's proleptic-Gregorian
* algorithms (the "chrono-Compatible Low-Level Date Algorithms" pair): the
* era-400 formulation keeps the year arithmetic exact for the full 32-bit
* int year range and the division by 146097 is compile-time constant.
* days_from_civil counts days since 1970-01-01 (negative before that);
* civil_from_days is its exact inverse and yields a 1-based month.
*
* floor_div/floor_mod give Euclidean division so that the day/time math
* stays correct for negative inputs (pre-1970 timestamps, out-of-range
* struct tm fields): floor_mod always returns a non-negative remainder.
*/
static long long
floor_div(long long a, long long b)
{
long long q = a / b;
long long r = a % b;
if (r != 0 && ((r < 0) != (b < 0)))
{
q -= 1;
}
return q;
}
static long long
floor_mod(long long a, long long b)
{
long long r = a % b;
if (r != 0 && ((r < 0) != (b < 0)))
{
r += b;
}
return r;
}
// NOLINTBEGIN(bugprone-easily-swappable-parameters,bugprone-reserved-identifier)
long long
days_from_civil(int y, int m, int d)
{
long long yy = y;
yy -= (m <= 2);
{
long long era = (yy >= 0 ? yy : yy - 399) / 400;
unsigned int yoe = (unsigned int)(yy - era * 400); /* [0, 399] */
unsigned int doy = (153u * (unsigned int)(m + (m > 2 ? -3 : 9)) + 2u) / 5u +
(unsigned int)d - 1u; /* [0, 365] */
unsigned int doe = yoe * 365u + yoe / 4u - yoe / 100u + doy; /* [0, 146096] */
return era * 146097 + (long long)doe - 719468;
}
}
void
civil_from_days(long long days, int *y, int *m, int *d)
{
long long z = days + 719468;
long long era = (z >= 0 ? z : z - 146096) / 146097;
unsigned int doe = (unsigned int)(z - era * 146097); /* [0, 146096] */
unsigned int yoe = (doe - doe / 1460u + doe / 36524u - doe / 146096u) / 365u; /* [0, 399] */
unsigned int doy = doe - (365u * yoe + yoe / 4u - yoe / 100u); /* [0, 365] */
unsigned int mp = (5u * doy + 2u) / 153u; /* [0, 11] */
unsigned int dd = doy - (153u * mp + 2u) / 5u + 1u; /* [1, 31] */
unsigned int mm = mp < 10u ? mp + 3u : mp - 9u; /* [1, 12] */
*y = (int)(yoe + era * 400) + (int)(mm <= 2u);
*m = (int)mm;
*d = (int)dd;
}
/*
* __fill_tm: materialize a struct tm from a local civil time expressed as
* seconds (the pseudo-UTC frame every TZ computation uses), the offset in
* effect, the DST flag, and the zone abbreviation to store in tm_zone.
* The caller supplies the fully resolved values; this function only splits
* local_sec into the civil fields.
*/
void
__fill_tm(long long local_sec, long gmtoff, int isdst, const char *zone, struct tm *out)
{
long long days = floor_div(local_sec, 86400);
long long tod = local_sec - days * 86400; /* [0, 86400) */
int y;
int m;
int d;
civil_from_days(days, &y, &m, &d);
out->tm_sec = (int)(tod % 60);
out->tm_min = (int)((tod / 60) % 60);
out->tm_hour = (int)(tod / 3600);
out->tm_mday = d;
out->tm_mon = m - 1;
out->tm_year = y - 1900;
out->tm_wday = (int)floor_mod(days + 4, 7); /* 1970-01-01 was a Thursday */
out->tm_yday = (int)(days - days_from_civil(y, 1, 1));
out->tm_isdst = isdst;
out->tm_gmtoff = gmtoff;
out->tm_zone = zone;
}
// NOLINTEND(bugprone-easily-swappable-parameters,bugprone-reserved-identifier)
/*
* tm_civil_seconds: the struct tm fields reduced to one linear "local
* civil" second count. Out-of-range fields fold in naturally: the month is
* renormalized into the year with Euclidean division, the day is carried as
* an offset from the first of the month, and the time of day just adds on
* (hours/minutes/seconds may be anything and spill across day boundaries).
*/
static long long
tm_civil_seconds(const struct tm *tm)
{
int y = tm->tm_year + 1900 + (int)floor_div((long long)tm->tm_mon, 12);
int m = (int)floor_mod((long long)tm->tm_mon, 12) + 1;
long long days = days_from_civil(y, m, 1) + (long long)tm->tm_mday - 1;
return days * 86400 + (long long)tm->tm_hour * 3600 + (long long)tm->tm_min * 60 +
(long long)tm->tm_sec;
}
/*
* mktime: interpret the broken-down fields as LOCAL civil time and return
* the corresponding UTC epoch. The DST regime is chosen from tm_isdst:
* positive forces daylight time, zero forces standard time, and negative
* (the "I don't know" value localtime fills) defers to the zone rules.
* POSIX requires the struct to be overwritten with the normalized fields,
* so __fill_tm runs on the resolved local time before returning. Overflow
* leaves the struct untouched and reports (time_t)-1 with errno EOVERFLOW.
*/
time_t
mktime(struct tm *tm)
{
const struct tz_state *st;
long long local = tm_civil_seconds(tm);
long off;
int isdst = 0;
long long epoch;
__tzset_lazy();
st = __tz_state();
off = tz_offset_for_local(st, local, tm->tm_isdst, &isdst);
if (__builtin_sub_overflow(local, (long long)off, &epoch))
{
errno = EOVERFLOW;
return (time_t)-1;
}
__fill_tm(local, off, isdst, isdst ? st->dst_name : st->std_name, tm);
return (time_t)epoch;
}
#if VLIBC_LEVEL_GE(2)
/*
* timegm: mktime interpreted as UTC — the same normalization, with a zero
* offset and a fixed "UTC" zone, so no timezone state is consulted.
*/
time_t
timegm(struct tm *tm)
{
long long local = tm_civil_seconds(tm);
long long epoch;
if (__builtin_sub_overflow(local, 0, &epoch))
{
errno = EOVERFLOW;
return (time_t)-1;
}
__fill_tm(local, 0, 0, "UTC", tm);
return (time_t)epoch;
}
#endif /* VLIBC_LEVEL_GE(2) */
+99
View File
@@ -0,0 +1,99 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <time.h>
#include "../internal/syscall.h"
/*
* nanosleep: high-resolution sleep for request.
*
* On EINTR the kernel writes the unslept remainder into rem (when rem is
* not NULL) and the sleep restarts from it, so a signal handler cannot
* shorten the requested interval by more than the time already slept —
* nanosleep only reports the interruption to the caller when rem is NULL.
* The loop therefore terminates on a completed sleep (0) or on any error
* the kernel cannot make progress from.
*/
int
nanosleep(const struct timespec *request, struct timespec *remain)
{
for (;;)
{
long r = syscall_ret(__syscall2(SYS_nanosleep, (long)request, (long)remain));
if (r == 0)
{
return 0;
}
if (errno == EINTR && remain != NULL)
{
request = remain;
continue;
}
return (int)r;
}
}
/*
* clock_nanosleep: nanosleep on an explicit clock.
*
* With TIMER_ABSTIME the request is an absolute expiry on clockid and the
* sleep cannot usefully restart, so an EINTR is reported to the caller;
* relative sleeps restart from the remainder like nanosleep. flags accepts
* exactly 0 or TIMER_ABSTIME (anything else is EINVAL before any syscall).
* Like nanosleep this reports failure as -1 with errno set rather than as
* a returned error number (POSIX permits either).
*/
int
clock_nanosleep(clockid_t clockid, int flags, const struct timespec *request,
struct timespec *remain)
{
if (flags != 0 && flags != TIMER_ABSTIME)
{
errno = EINVAL;
return -1;
}
for (;;)
{
long r = syscall_ret(__syscall4(SYS_clock_nanosleep, (long)clockid, (long)flags,
(long)request, (long)remain));
if (r == 0)
{
return 0;
}
if (!(flags & TIMER_ABSTIME) && errno == EINTR && remain != NULL)
{
request = remain;
continue;
}
return (int)r;
}
}
#if VLIBC_LEVEL_GE(2)
/*
* usleep: microsecond sleep (level 2). nanosleep does the sleeping and
* restarts from the remainder on interruption, so the whole requested
* interval always elapses; a genuine error is reported as (useconds_t)-1.
*/
useconds_t
usleep(useconds_t usec)
{
struct timespec ts;
ts.tv_sec = usec / 1000000;
ts.tv_nsec = (long)(usec % 1000000) * 1000L;
if (nanosleep(&ts, &ts) != 0)
{
return (useconds_t)-1;
}
return 0;
}
#endif /* VLIBC_LEVEL_GE(2) */
+26
View File
@@ -0,0 +1,26 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
/*
* sleep: whole-second sleep for seconds.
*
* nanosleep does the sleeping and restarts from the remainder on signal
* interruption (remain points back at the same struct), so a signal can
* only ever extend the wall time spent in sleep, never truncate it; the
* only non-zero return is a genuine error, reported with the (unconsumed)
* requested seconds, as POSIX requires.
*/
unsigned int
sleep(unsigned int seconds)
{
struct timespec ts = {(time_t)seconds, 0};
if (nanosleep(&ts, &ts) != 0)
{
return (unsigned int)ts.tv_sec;
}
return 0;
}
+419
View File
@@ -0,0 +1,419 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
#include "time_impl.h"
/*
* strftime: C-locale formatting of broken-down time.
*
* Conversion coverage is the full POSIX set (%a %A %b %B %c %C %d %D %e %F
* %g %G %h %H %I %j %m %M %n %p %r %R %S %t %T %u %U %V %w %W %x %X %y %Y
* %z %Z %%). The composite conversions expand to the documented POSIX
* C-locale forms; %e is space-padded; the ISO year/week conversions %g/%G/
* %V follow the weekday-of-4-January rule; %z renders tm_gmtoff as a signed
* HHMM. GNU-style modifier characters ('-' '_' '0' padding flags, '^' '#',
* width digits, 'E'/'O' alternatives) are accepted and ignored, so format
* strings written for glibc do not error out.
*
* The writer counts everything it would emit, so an oversized buffer is
* detected exactly (returns 0) without first needing the output to fit.
*/
static const char *const wd_full[7] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
static const char *const wd_abbr[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *const mon_full[12] = {"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};
static const char *const mon_abbr[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
static long long
floor_mod(long long a, long long b)
{
long long r = a % b;
if (r != 0 && ((r < 0) != (b < 0)))
{
r += b;
}
return r;
}
struct out
{
char *s;
size_t cap;
size_t n;
};
static void
out_ch(struct out *o, char c)
{
if (o->n < o->cap)
{
o->s[o->n] = c;
}
o->n++;
}
static void
out_mem(struct out *o, const char *p, size_t len)
{
size_t i;
for (i = 0; i < len; i++)
{
out_ch(o, p[i]);
}
}
static void
out_str(struct out *o, const char *s)
{
while (*s != '\0')
{
out_ch(o, *s);
s++;
}
}
/* Emit the (non-negative) decimal v, left-padded with pad to width chars. */
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
static void
out_num(struct out *o, long long v, int width, char pad)
{
char tmp[24];
int i = (int)sizeof tmp;
do
{
tmp[--i] = (char)('0' + v % 10);
v /= 10;
} while (v > 0);
while ((int)sizeof tmp - i < width)
{
tmp[--i] = pad;
}
out_mem(o, tmp + i, (size_t)(sizeof tmp - i));
}
// NOLINTEND(bugprone-easily-swappable-parameters)
/* Emit a year: optional '-' then at least four digits. */
static void
out_year(struct out *o, int year)
{
if (year < 0)
{
out_ch(o, '-');
out_num(o, -(long long)year, 4, '0');
}
else
{
out_num(o, year, 4, '0');
}
}
/* Weekday/month indexes, normalized so stray values cannot index out of
* range (negative inputs wrap into 0..6 / 0..11). */
static int
wd_index(int w)
{
w %= 7;
return w < 0 ? w + 7 : w;
}
static int
mon_index(int m)
{
m %= 12;
return m < 0 ? m + 12 : m;
}
/* ISO-8601 year and week (week containing the year's first Thursday; the
* week whose Monday is closest to 4 January). Week 1 may belong to the
* adjacent calendar year. */
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
static void
iso_week_year(const struct tm *tm, int *year_out, int *week_out)
{
int y = tm->tm_year + 1900;
long long day = days_from_civil(y, 1, 1) + (long long)tm->tm_yday;
long long k = floor_mod(day + 3, 7); /* 0 = Monday */
long long th = day + (k <= 3 ? 3 - k : -(k - 3));
long long th_jan1;
int ty;
int tm_;
int td;
civil_from_days(th, &ty, &tm_, &td);
(void)tm_;
(void)td;
th_jan1 = days_from_civil(ty, 1, 1);
*year_out = ty;
*week_out = (int)((th - th_jan1) / 7) + 1;
}
// NOLINTEND(bugprone-easily-swappable-parameters)
static void
fmt_run(struct out *o, const struct tm *tm, const char *f);
/* Format one conversion (after '%'). Composites expand recursively. */
static void
fmt_one(struct out *o, const struct tm *tm, char c)
{
int h;
int wd;
switch (c)
{
case 'a':
out_str(o, wd_abbr[wd_index(tm->tm_wday)]);
break;
case 'A':
out_str(o, wd_full[wd_index(tm->tm_wday)]);
break;
case 'b':
case 'h':
out_str(o, mon_abbr[mon_index(tm->tm_mon)]);
break;
case 'B':
out_str(o, mon_full[mon_index(tm->tm_mon)]);
break;
case 'c':
fmt_run(o, tm, "%a %b %e %H:%M:%S %Y");
break;
case 'C':
out_num(o, (long long)(tm->tm_year + 1900) / 100, 2, '0');
break;
case 'd':
out_num(o, tm->tm_mday, 2, '0');
break;
case 'D':
fmt_run(o, tm, "%m/%d/%y");
break;
case 'e':
out_num(o, tm->tm_mday, 2, ' ');
break;
case 'F':
fmt_run(o, tm, "%Y-%m-%d");
break;
case 'g':
case 'G':
case 'V':
{
int iy;
int iw;
iso_week_year(tm, &iy, &iw);
if (c == 'V')
{
out_num(o, iw, 2, '0');
}
else if (c == 'g')
{
int yy = iy % 100;
if (yy < 0)
{
yy += 100;
}
out_num(o, yy, 2, '0');
}
else
{
out_year(o, iy);
}
break;
}
case 'H':
out_num(o, tm->tm_hour, 2, '0');
break;
case 'I':
h = tm->tm_hour % 12;
if (h == 0)
{
h = 12;
}
out_num(o, h, 2, '0');
break;
case 'j':
out_num(o, (long long)tm->tm_yday + 1, 3, '0');
break;
case 'm':
out_num(o, tm->tm_mon + 1, 2, '0');
break;
case 'M':
out_num(o, tm->tm_min, 2, '0');
break;
case 'n':
out_ch(o, '\n');
break;
case 'p':
out_str(o, tm->tm_hour < 12 ? "AM" : "PM");
break;
case 'r':
fmt_run(o, tm, "%I:%M:%S %p");
break;
case 'R':
fmt_run(o, tm, "%H:%M");
break;
case 'S':
out_num(o, tm->tm_sec, 2, '0');
break;
case 't':
out_ch(o, '\t');
break;
case 'T':
fmt_run(o, tm, "%H:%M:%S");
break;
case 'u':
wd = tm->tm_wday;
out_num(o, wd == 0 ? 7 : wd, 1, '0');
break;
case 'U':
/* Week of the year, Sunday as first day; days before the first
* Sunday are week 0. */
out_num(o, (long long)(tm->tm_yday + 7 - wd_index(tm->tm_wday)) / 7, 2, '0');
break;
case 'w':
out_num(o, tm->tm_wday, 1, '0');
break;
case 'W':
/* Week of the year, Monday as first day. */
wd = (wd_index(tm->tm_wday) + 6) % 7;
out_num(o, (long long)(tm->tm_yday + 7 - wd) / 7, 2, '0');
break;
case 'x':
fmt_run(o, tm, "%m/%d/%y");
break;
case 'X':
fmt_run(o, tm, "%H:%M:%S");
break;
case 'y':
{
int yy = (tm->tm_year + 1900) % 100;
if (yy < 0)
{
yy += 100;
}
out_num(o, yy, 2, '0');
break;
}
case 'Y':
out_year(o, tm->tm_year + 1900);
break;
case 'z':
{
long off = tm->tm_gmtoff;
long mag;
if (off < 0)
{
out_ch(o, '-');
mag = -off;
}
else
{
out_ch(o, '+');
mag = off;
}
out_num(o, mag / 3600, 2, '0');
out_num(o, (mag % 3600) / 60, 2, '0');
break;
}
case 'Z':
if (tm->tm_zone != NULL && tm->tm_zone[0] != '\0')
{
out_str(o, tm->tm_zone);
}
break;
case '%':
out_ch(o, '%');
break;
default:
out_ch(o, c);
break;
}
}
/* Scan a full format string; called by strftime and by the composites. */
void
fmt_run(struct out *o, const struct tm *tm, const char *f)
{
while (*f != '\0')
{
char c = *f;
if (c != '%')
{
out_ch(o, c);
f++;
continue;
}
f++;
while (*f == '-' || *f == '_' || *f == '0' || *f == '^' || *f == '#')
{
f++;
}
while (*f >= '0' && *f <= '9')
{
f++;
}
if (*f == 'E' || *f == 'O')
{
f++;
}
if (*f == '\0')
{
out_ch(o, '%');
break;
}
fmt_one(o, tm, *f);
f++;
}
}
/*
* strftime: format tm per format into s. Returns the number of bytes placed
* (excluding the terminating NUL), or 0 when the full result (NUL included)
* does not fit in maxsize.
*/
size_t
strftime(char *restrict s, size_t maxsize, const char *restrict format,
const struct tm *restrict timeptr)
{
struct out o;
o.s = s;
o.cap = maxsize;
o.n = 0;
fmt_run(&o, timeptr, format);
if (o.n < maxsize)
{
s[o.n] = '\0';
return o.n;
}
return 0;
}
#if VLIBC_LEVEL_GE(2)
/*
* strftime_l: strftime with an explicit locale. No locale support exists
* yet, so the locale argument is ignored and the C-locale output is
* produced (the fixed behavior of the whole file).
*/
size_t
strftime_l(char *restrict s, size_t maxsize, const char *restrict format,
const struct tm *restrict timeptr, void *loc)
{
(void)loc;
return strftime(s, maxsize, format, timeptr);
}
#endif /* VLIBC_LEVEL_GE(2) */
+457
View File
@@ -0,0 +1,457 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
#if VLIBC_LEVEL_GE(2)
/*
* strptime: reverse of the strftime conversion set (POSIX, level 2).
*
* Accepts the same conversions strftime emits — the day/month names
* case-insensitively, the composite formats %c %D %F %r %R %T %x %X, the
* numeric fields %C %d %e %g %G %H %I %j %m %M %S %u %U %V %w %W %y %Y,
* %p (which converts a 12-hour %I into a 24-hour tm_hour), %z (into
* tm_gmtoff), %Z (consumed, no storage), %n/%t and literal whitespace
* (which both match any run of whitespace), and %%. GNU modifier
* characters before a conversion are accepted and ignored, mirroring
* strftime. Returns a pointer to the first input character not consumed;
* NULL when nothing was matched or a conversion fails mid-way.
*/
static int
is_space(char c)
{
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
}
static int
is_digit(char c)
{
return c >= '0' && c <= '9';
}
static int
is_alpha(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
static char
lower(char c)
{
if (c >= 'A' && c <= 'Z')
{
return (char)(c - 'A' + 'a');
}
return c;
}
/* Case-insensitive whole-word match; the word must end at a non-letter. */
static int
word_matches(const char *s, const char *word)
{
size_t i = 0;
while (word[i] != '\0')
{
if (lower(s[i]) != lower(word[i]))
{
return 0;
}
i++;
}
return !is_alpha(s[i]);
}
static const char *const wd_full[7] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"};
static const char *const wd_abbr[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static const char *const mon_full[12] = {"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};
static const char *const mon_abbr[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
/* Match a month name; returns the advanced pointer or NULL. */
static const char *
match_mon(const char *s, int *mon_out)
{
int i;
for (i = 0; i < 12; i++)
{
if (word_matches(s, mon_full[i]))
{
*mon_out = i;
return s + 7;
}
}
for (i = 0; i < 12; i++)
{
if (word_matches(s, mon_abbr[i]))
{
*mon_out = i;
return s + 3;
}
}
return NULL;
}
/* Match a weekday name; returns the advanced pointer or NULL. */
static const char *
match_wday(const char *s, int *wday_out)
{
int i;
for (i = 0; i < 7; i++)
{
if (word_matches(s, wd_full[i]))
{
*wday_out = i;
return s + 7;
}
}
for (i = 0; i < 7; i++)
{
if (word_matches(s, wd_abbr[i]))
{
*wday_out = i;
return s + 3;
}
}
return NULL;
}
struct parse_ctx
{
int am_pm; /* 0 = not seen, 1 = AM, 2 = PM */
};
static const char *
parse_num(const char *s, int width, int *out)
{
int v = 0;
int n = 0;
while (is_space(*s))
{
s++;
}
while (n < width && is_digit(*s))
{
v = v * 10 + (*s - '0');
s++;
n++;
}
if (n == 0)
{
return NULL;
}
*out = v;
return s;
}
/* Advance past any run of whitespace in s. */
static const char *
skip_ws(const char *s)
{
while (is_space(*s))
{
s++;
}
return s;
}
static const char *
parse_one(const char *s, const char *f, struct tm *tm, struct parse_ctx *ctx);
/* strptime_impl: match one conversion directive at f (f is the conv char). */
static const char *
parse_conv(const char *s, char c, struct tm *tm, struct parse_ctx *ctx)
{
const char *p;
int v;
int idx;
switch (c)
{
case 'a':
case 'A':
return match_wday(s, &tm->tm_wday);
case 'b':
case 'B':
case 'h':
return match_mon(s, &tm->tm_mon);
case 'c':
return parse_one(s, "%a %b %e %H:%M:%S %Y", tm, ctx);
case 'C':
return parse_num(s, 2, &v);
case 'd':
case 'e':
p = parse_num(s, 2, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_mday = v;
return p;
case 'D':
case 'x':
return parse_one(s, "%m/%d/%y", tm, ctx);
case 'F':
return parse_one(s, "%Y-%m-%d", tm, ctx);
case 'g':
case 'G':
case 'V':
return parse_num(s, 2, &v);
case 'H':
p = parse_num(s, 2, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_hour = v;
return p;
case 'I':
p = parse_num(s, 2, &v);
if (p == NULL)
{
return NULL;
}
if (ctx->am_pm == 1)
{
tm->tm_hour = v == 12 ? 0 : v;
}
else if (ctx->am_pm == 2)
{
tm->tm_hour = v == 12 ? 12 : v + 12;
}
else
{
tm->tm_hour = v;
}
return p;
case 'j':
p = parse_num(s, 3, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_yday = v - 1;
return p;
case 'm':
p = parse_num(s, 2, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_mon = v - 1;
return p;
case 'M':
p = parse_num(s, 2, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_min = v;
return p;
case 'n':
case 't':
return skip_ws(s);
case 'p':
if (word_matches(s, "AM"))
{
ctx->am_pm = 1;
if (tm->tm_hour == 12)
{
tm->tm_hour = 0;
}
return s + 2;
}
if (word_matches(s, "PM"))
{
ctx->am_pm = 2;
if (tm->tm_hour >= 1 && tm->tm_hour <= 12)
{
tm->tm_hour += tm->tm_hour == 12 ? 0 : 12;
}
return s + 2;
}
return NULL;
case 'r':
return parse_one(s, "%I:%M:%S %p", tm, ctx);
case 'R':
return parse_one(s, "%H:%M", tm, ctx);
case 'S':
p = parse_num(s, 2, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_sec = v;
return p;
case 'T':
case 'X':
return parse_one(s, "%H:%M:%S", tm, ctx);
case 'u':
p = parse_num(s, 1, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_wday = v == 7 ? 0 : v;
return p;
case 'U':
case 'W':
return parse_num(s, 2, &v);
case 'w':
p = parse_num(s, 1, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_wday = v;
return p;
case 'y':
p = parse_num(s, 2, &v);
if (p == NULL)
{
return NULL;
}
/* POSIX pivot: 69-99 -> 1969-1999, 00-68 -> 2000-2068. */
tm->tm_year = v <= 68 ? v + 100 : v;
return p;
case 'Y':
p = parse_num(s, 9, &v);
if (p == NULL)
{
return NULL;
}
tm->tm_year = v - 1900;
return p;
case 'z':
idx = 0;
if (s[idx] == '+' || s[idx] == '-')
{
int neg = s[idx] == '-';
int hh;
int mm;
idx++;
p = parse_num(s + idx, 2, &hh);
if (p == NULL)
{
return NULL;
}
idx += (int)(p - (s + idx));
if (s[idx] == ':')
{
idx++;
}
p = parse_num(s + idx, 2, &mm);
if (p == NULL)
{
return NULL;
}
idx += (int)(p - (s + idx));
v = hh * 3600 + mm * 60;
tm->tm_gmtoff = neg ? -v : v;
return p;
}
return NULL;
case 'Z':
/* A timezone name: consume one alphabetic run (no storage). */
while (is_alpha(*s))
{
s++;
}
return s;
case '%':
if (*s == '%')
{
return s + 1;
}
return NULL;
default:
return NULL;
}
}
/* Match s against the format starting at f; returns the input position. */
static const char *
parse_one(const char *s, const char *f, struct tm *tm, struct parse_ctx *ctx)
{
while (*f != '\0')
{
char c = *f;
if (c == '%')
{
f++;
while (*f == '-' || *f == '_' || *f == '0' || *f == '^' || *f == '#')
{
f++;
}
while (is_digit(*f))
{
f++;
}
if (*f == 'E' || *f == 'O')
{
f++;
}
if (*f == '\0')
{
return NULL; /* dangling '%' cannot match */
}
s = parse_conv(s, *f, tm, ctx);
if (s == NULL)
{
return NULL;
}
f++;
}
else if (is_space(c))
{
while (is_space(*f))
{
f++;
}
s = skip_ws(s);
}
else
{
if (*s != c)
{
return NULL;
}
s++;
f++;
}
}
return s;
}
char *
strptime(const char *restrict s, const char *restrict format, struct tm *restrict tm)
{
const char *start = s;
struct parse_ctx ctx;
const char *end;
ctx.am_pm = 0;
end = parse_one(s, format, tm, &ctx);
if (end == NULL)
{
return NULL;
}
if (end == start)
{
return NULL; /* nothing converted */
}
return (char *)end;
}
#endif /* VLIBC_LEVEL_GE(2) */
+41
View File
@@ -0,0 +1,41 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
/*
* time: the current wall-clock time in seconds since the Epoch, read via
* clock_gettime(CLOCK_REALTIME) (the dedicated SYS_time syscall is just a
* gettimeofday wrapper with no added precision). The value is stored
* through t when t is not NULL; time(NULL) is the common "just the value"
* call, so NULL must not be dereferenced.
*/
time_t
time(time_t *t)
{
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) != 0)
{
return (time_t)-1;
}
if (t != NULL)
{
*t = ts.tv_sec;
}
return ts.tv_sec;
}
/*
* difftime: the difference in seconds between two calendar times.
*
* Plain double subtraction needs no libm (the arithmetic is emitted
* inline); returning the exact time_t difference through double preserves
* it for any representable range of times.
*/
double
difftime(time_t a, time_t b)
{
return (double)a - (double)b;
}
+72
View File
@@ -0,0 +1,72 @@
#ifndef VLIBC_TIME_IMPL_H
#define VLIBC_TIME_IMPL_H
/*
* vlibc — internal time/TZ implementation interface.
*
* Shared contract between the mktime/TZ engine (src/time/mktime.c,
* src/time/tzset.c) and, later, the formatting layer. Everything here is
* hidden library-internal API: the public surface stays in <time.h>.
*
* A parsed POSIX TZ state describes one standard-time zone plus an
* optional daylight-saving regime. All offsets are seconds EAST of UTC
* (EST is -18000, EDT is -14400); the POSIX TZ offset text is seconds
* WEST and gets negated at parse time. The two transition rules delimit
* the DST interval in local civil seconds for a given year, exactly as a
* POSIX "std offset[dst[offset][,start[/time],end[/time]]]" string does.
*/
#include "../internal/libc.h"
#include <time.h>
/*
* One DST transition rule. kind selects the three POSIX spellings:
* 0: Mm.w.d — month 1-12, week 1-5 (5 = last), weekday 0-6 (Sunday 0);
* 1: Jn — Julian day 1-365, 29 February never counted;
* 2: n — zero-based day 0-365, leap day counted.
* time is the transition instant in seconds after local midnight (the
* default 02:00:00 = 7200).
*/
struct tz_rule
{
int kind;
int month;
int week;
int wday;
long day;
long time;
};
/* Parsed POSIX TZ state (see the file comment for the offset sign). */
struct tz_state
{
char std_name[16];
char dst_name[16];
long std_off;
long dst_off;
int has_dst;
struct tz_rule start;
struct tz_rule end;
};
// NOLINTBEGIN(bugprone-reserved-identifier)
hidden void
__tzset_lazy(void);
hidden const struct tz_state *
__tz_state(void);
hidden int
tz_rule_contains(const struct tz_state *s, long long local_sec);
hidden long
tz_offset_for_local(const struct tz_state *s, long long local_sec, int isdst_hint, int *isdst_out);
hidden long
tz_offset_at_utc(const struct tz_state *s, long long utc_sec, int *isdst_out);
hidden void
__fill_tm(long long local_sec, long gmtoff, int isdst, const char *zone, struct tm *out);
hidden long long
days_from_civil(int y, int m, int d);
hidden void
civil_from_days(long long days, int *y, int *m, int *d);
// NOLINTEND(bugprone-reserved-identifier)
#endif /* VLIBC_TIME_IMPL_H */
+173
View File
@@ -0,0 +1,173 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
#include <string.h>
#include "../internal/syscall.h"
/*
* POSIX interval timers (level 2), thin wrappers over SYS_timer_create(222)/
* SYS_timer_settime(223)/SYS_timer_gettime(224)/SYS_timer_getoverrun(225)/
* SYS_timer_delete(226).
*
* The public <time.h> only forward-declares struct sigevent and struct
* itimerspec (their canonical home is <signal.h>, which does not exist in
* vlibc yet), so the kernel ABI mirror of each is defined here, in this
* translation unit, and consumers that build their own struct sigevent /
* struct itimerspec must lay them out to match the kernel exactly:
*
* struct sigevent, x86_64 Linux (128 bytes, kernel uapi layout):
* offset 0: union sigval sigev_value (8 bytes)
* offset 8: int sigev_signo
* offset 12: int sigev_notify (0 SIGEV_SIGNAL,
* 1 SIGEV_NONE,
* 4 SIGEV_THREAD_ID)
* offset 16: union { int _tid; ... } (SIGEV_THREAD_ID target)
* rest: padding, zero.
* The kernel copy_from_user's only the leading part of this struct it
* knows about (64 bytes on current kernels, 128 on older ones); a full
* 128-byte buffer satisfies both, so the kernel mirror below is pinned
* to 128 bytes and always zero-initialized.
*
* struct itimerspec (32 bytes): struct timespec it_interval followed by
* struct timespec it_value, the kernel layout.
*
* timer_t is <sys/types.h>'s opaque pointer type; the kernel hands out
* plain int timer ids, so timer_create encodes the id into the timer_t
* value (cast to a pointer) and every timer_* wrapper decodes it back.
*/
#if VLIBC_LEVEL_GE(2)
/* SIGEV_SIGNAL, the notification kind the POSIX default timer_create(NULL)
* resolves to. */
#define VLIBC_SIGEV_SIGNAL 0
/* SIGALRM, the POSIX default signal for timer_create(NULL). */
#define VLIBC_SIGALRM 14
/* Kernel sigevent mirror: see the file comment for the layout. */
struct sigevent
{
union
{
void *sival_ptr;
int sival_int;
} sigev_value; /* 0: payload for the notification */
int sigev_signo; /* 8: signal to raise (SIGEV_SIGNAL) */
int sigev_notify; /* 12: notification mechanism */
union
{
int sigev_tid; /* 16: target thread (SIGEV_THREAD_ID) */
int sigev_pad[28]; /* padding to the 128-byte kernel struct */
} sigev_un;
};
_Static_assert(sizeof(struct sigevent) == 128, "struct sigevent must be the 128-byte kernel ABI");
_Static_assert(_Alignof(struct sigevent) == 8, "struct sigevent alignment");
/* Kernel itimerspec mirror: interval plus next expiry, two timespecs. */
struct itimerspec
{
struct timespec it_interval;
struct timespec it_value;
};
/* Decode the kernel timer id carried in an opaque timer_t. */
static int
timer_id(timer_t t)
{
return (int)(long)t;
}
/*
* timer_create: create a per-process timer on clockid and store its id
* through timerid.
*
* A NULL evp selects the POSIX default (SIGEV_SIGNAL with SIGALRM), which
* the kernel would also apply to a NULL sigevent; for a real evp only the
* leading 24 bytes the kernel consumes are copied out of the caller's
* struct (value/signo/notify/tid all live there), with the rest of the
* 128-byte kernel image left zero. The kernel validates the notification
* kind and signal itself.
*/
int
timer_create(clockid_t clockid, const struct sigevent *restrict evp, timer_t *restrict timerid)
{
struct sigevent kev;
int id;
long r;
memset(&kev, 0, sizeof kev);
if (evp == NULL)
{
kev.sigev_notify = VLIBC_SIGEV_SIGNAL;
kev.sigev_signo = VLIBC_SIGALRM;
}
else
{
memcpy(&kev, evp, 24);
}
r = syscall_ret(__syscall3(SYS_timer_create, (long)clockid, (long)&kev, (long)&id));
if (r != 0)
{
return (int)r;
}
*timerid = (timer_t)(long)id;
return 0;
}
/*
* timer_delete: destroy the timer, releasing its kernel resources and
* cancelling any pending expiry. A timer id is single-use: deleting an
* already-deleted id fails with EINVAL.
*/
int
timer_delete(timer_t timerid)
{
return syscall_ret(__syscall1(SYS_timer_delete, (long)timer_id(timerid)));
}
/*
* timer_settime: arm (flags == 0) or re-arm the timer. With TIMER_ABSTIME
* it_value is an absolute expiry on the timer's creation clock; otherwise
* it is relative to now. When old_value is not NULL the previous setting is
* stored there. SYS_timer_settime takes (timerid, flags, new, old).
*/
int
timer_settime(timer_t timerid, int flags, const struct itimerspec *restrict new_value,
struct itimerspec *restrict old_value)
{
return syscall_ret(__syscall4(SYS_timer_settime, (long)timer_id(timerid), (long)flags,
(long)new_value, (long)old_value));
}
/*
* timer_gettime: the timer's current interval and the time remaining until
* its next expiry (0 once expired). Return 0 or -1 with errno set.
*/
int
timer_gettime(timer_t timerid, struct itimerspec *curr_value)
{
return syscall_ret(__syscall2(SYS_timer_gettime, (long)timer_id(timerid), (long)curr_value));
}
/*
* timer_getoverrun: expirations of a periodic timer that were missed
* between the signal delivery just consumed and the one before it, capped
* at DELAYTIMER_MAX. Returns -1 with errno set when no timer signal is
* pending for this process.
*/
int
timer_getoverrun(timer_t timerid)
{
return (int)syscall_ret(__syscall1(SYS_timer_getoverrun, (long)timer_id(timerid)));
}
#endif /* VLIBC_LEVEL_GE(2) */
+24
View File
@@ -0,0 +1,24 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <time.h>
/*
* timespec_get: ISO C interface over clock_gettime(CLOCK_REALTIME).
* TIME_UTC is the only supported base; any other base, or a clock failure,
* yields 0 (no errno), and success returns the base unchanged.
*/
int
timespec_get(struct timespec *ts, int base)
{
if (base != TIME_UTC)
{
return 0;
}
if (clock_gettime(CLOCK_REALTIME, ts) != 0)
{
return 0;
}
return base;
}
+652
View File
@@ -0,0 +1,652 @@
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "time_impl.h"
/*
* POSIX TZ engine (level-independent core of tzset/localtime).
*
* The one parsed state object mirrors the caller-visible libc timezone
* globals: tzset() re-reads the TZ environment variable on demand and
* (at level 2) republishes tzname/daylight/timezone from the result, while
* localtime/mktime consume the state lazily through __tzset_lazy. Parsing
* is deliberately lenient-fail: any syntax error, an empty TZ, or no TZ at
* all yields the UTC default (std "UTC", zero offset, no DST), never a
* crash and never a partial zone.
*
* POSIX TZ grammar (the subset vlibc accepts):
* std offset[dst[offset][,start[/time],end[/time]]]
* std/dst are 3+ alphabetic characters (or <quoted>); offset is
* [+-]hh[:mm[:ss]], the amount ADDED to local time to reach UTC, so the
* stored EAST-of-UTC std_off is its negation ("EST5" -> std_off -18000).
* An omitted dst offset defaults to standard plus one hour. Rules use the
* Mm.w.d, Jn and n spellings with an optional /time (default 02:00:00);
* when DST is present but no rules are given the US M3.2.0/M11.1.0 pair is
* assumed.
*/
static struct tz_state tz_state;
static int tz_parsed;
/* Euclidean helpers (same definitions as mktime.c, kept per-TU static). */
static long long
floor_div(long long a, long long b)
{
long long q = a / b;
long long r = a % b;
if (r != 0 && ((r < 0) != (b < 0)))
{
q -= 1;
}
return q;
}
static long long
floor_mod(long long a, long long b)
{
long long r = a % b;
if (r != 0 && ((r < 0) != (b < 0)))
{
r += b;
}
return r;
}
static int
tz_isdigit(char c)
{
return c >= '0' && c <= '9';
}
static int
tz_isalpha(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
static int
tz_is_leap(int y)
{
return (y % 4 == 0 && y % 100 != 0) || y % 400 == 0;
}
/*
* Parse a run of digits at *ip into *out; the run must be non-empty.
*/
static int
tz_parse_digits(const char *s, int *ip, long *out)
{
int i = *ip;
long v = 0;
if (!tz_isdigit(s[i]))
{
return 0;
}
while (tz_isdigit(s[i]))
{
v = v * 10 + (long)(s[i] - '0');
i++;
}
*out = v;
*ip = i;
return 1;
}
/*
* Parse an offset [+-]hh[:mm[:ss]] into signed seconds ("added to local
* time to reach UTC"; negation to the east-of-UTC convention happens in
* the caller).
*/
static int
tz_parse_offset(const char *s, int *ip, long *out)
{
int i = *ip;
int neg = 0;
long h;
long m = 0;
long sec = 0;
if (s[i] == '+' || s[i] == '-')
{
neg = (s[i] == '-');
i++;
}
if (!tz_parse_digits(s, &i, &h))
{
return 0;
}
if (s[i] == ':')
{
i++;
if (!tz_parse_digits(s, &i, &m))
{
return 0;
}
if (s[i] == ':')
{
i++;
if (!tz_parse_digits(s, &i, &sec))
{
return 0;
}
}
}
*out = (h * 3600 + m * 60 + sec) * (neg ? -1 : 1);
*ip = i;
return 1;
}
/* Parse a rule transition time hh[:mm[:ss]] (no sign; seconds of day). */
static int
tz_parse_rule_time(const char *s, int *ip, long *out)
{
int i = *ip;
long h;
long m = 0;
long sec = 0;
if (!tz_parse_digits(s, &i, &h) || h > 24)
{
return 0;
}
if (s[i] == ':')
{
i++;
if (!tz_parse_digits(s, &i, &m) || m > 59)
{
return 0;
}
if (s[i] == ':')
{
i++;
if (!tz_parse_digits(s, &i, &sec) || sec > 59)
{
return 0;
}
}
}
*out = h * 3600 + m * 60 + sec;
*ip = i;
return 1;
}
/*
* Parse a zone name: either <...> (any bytes, stored truncated to the
* buffer) or 3+ alphabetic characters.
*/
static int
tz_parse_name(const char *s, int *ip, char *out, size_t cap)
{
int i = *ip;
size_t n = 0;
if (s[i] == '<')
{
i++;
while (s[i] != '\0' && s[i] != '>')
{
if (n + 1 < cap)
{
out[n++] = s[i];
}
i++;
}
if (s[i] != '>')
{
return 0;
}
i++;
if (n == 0)
{
return 0;
}
}
else
{
while (tz_isalpha(s[i]))
{
if (n + 1 < cap)
{
out[n++] = s[i];
}
i++;
}
if (n < 3)
{
return 0;
}
}
out[n] = '\0';
*ip = i;
return 1;
}
/*
* Parse one transition rule (Mm.w.d, Jn, or n), optionally followed by a
* /time suffix; an omitted time defaults to 02:00:00 (7200). Ranges are
* validated so a malformed rule degrades to the UTC default.
*/
static int
tz_parse_rule(const char *s, int *ip, struct tz_rule *r)
{
int i = *ip;
long v;
r->kind = 0;
r->month = 0;
r->week = 0;
r->wday = 0;
r->day = 0;
r->time = 7200;
if (s[i] == 'M')
{
i++;
if (!tz_parse_digits(s, &i, &v) || v < 1 || v > 12)
{
return 0;
}
r->month = (int)v;
if (s[i] != '.')
{
return 0;
}
i++;
if (!tz_parse_digits(s, &i, &v) || v < 1 || v > 5)
{
return 0;
}
r->week = (int)v;
if (s[i] != '.')
{
return 0;
}
i++;
if (!tz_parse_digits(s, &i, &v) || v > 6)
{
return 0;
}
r->wday = (int)v;
}
else if (s[i] == 'J')
{
i++;
if (!tz_parse_digits(s, &i, &v) || v < 1 || v > 365)
{
return 0;
}
r->kind = 1;
r->day = v;
}
else
{
if (!tz_parse_digits(s, &i, &v) || v < 0 || v > 365)
{
return 0;
}
r->kind = 2;
r->day = v;
}
if (s[i] == '/')
{
i++;
if (!tz_parse_rule_time(s, &i, &r->time))
{
return 0;
}
}
*ip = i;
return 1;
}
/* Parse the full TZ string into st; 1 on success, 0 to fall back to UTC. */
static int
tz_parse(const char *s, struct tz_state *st)
{
int i = 0;
struct tz_state tmp;
long west;
int have_dst;
memset(&tmp, 0, sizeof tmp);
tmp.start.time = 7200; /* 02:00:00 */
tmp.end.time = 7200;
if (s[0] == ':')
{
return 0; /* ":rest-of-line" spelling is not supported; UTC. */
}
if (!tz_parse_name(s, &i, tmp.std_name, sizeof tmp.std_name))
{
return 0;
}
if (!tz_parse_offset(s, &i, &west))
{
return 0;
}
tmp.std_off = -west; /* east-of-UTC storage */
have_dst = 0;
if (s[i] != '\0' && s[i] != ',')
{
if (s[i] == '<' || tz_isalpha(s[i]))
{
if (!tz_parse_name(s, &i, tmp.dst_name, sizeof tmp.dst_name))
{
return 0;
}
have_dst = 1;
if (s[i] != '\0' && s[i] != ',')
{
if (!tz_parse_offset(s, &i, &west))
{
return 0;
}
tmp.dst_off = -west;
}
else
{
tmp.dst_off = tmp.std_off + 3600;
}
}
else
{
return 0;
}
}
tmp.has_dst = have_dst;
if (have_dst)
{
if (s[i] == ',')
{
i++;
if (!tz_parse_rule(s, &i, &tmp.start) || s[i] != ',')
{
return 0;
}
i++;
if (!tz_parse_rule(s, &i, &tmp.end))
{
return 0;
}
}
else
{
/* Default transition rules: US second Sunday in March / first
* Sunday in November, both at 02:00:00 local. */
tmp.start.kind = 0;
tmp.start.month = 3;
tmp.start.week = 2;
tmp.start.wday = 0;
tmp.end.kind = 0;
tmp.end.month = 11;
tmp.end.week = 1;
tmp.end.wday = 0;
}
}
if (s[i] != '\0')
{
return 0;
}
*st = tmp;
return 1;
}
/*
* tz_rule_day: day count (days since 1970-01-01) of the calendar day on
* which rule r transitions in `year`, per the rule's spelling.
*/
static long long
tz_rule_day(int year, const struct tz_rule *r)
{
if (r->kind == 0)
{
static const int mlen[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (r->week <= 4)
{
/* w-th weekday of the month. */
int first_wday = (int)floor_mod(days_from_civil(year, r->month, 1) + 4, 7);
int dom = 1 + (int)floor_mod((long long)r->wday - first_wday, 7) + 7 * (r->week - 1);
return days_from_civil(year, r->month, dom);
}
else
{
/* Last weekday of the month (week 5 = "last"). */
int dim = mlen[r->month - 1];
int last_wday;
if (r->month == 2 && tz_is_leap(year))
{
dim++;
}
last_wday = (int)floor_mod(days_from_civil(year, r->month, dim) + 4, 7);
return days_from_civil(year, r->month,
dim - (int)floor_mod((long long)last_wday - r->wday, 7));
}
}
else
{
long long doy;
if (r->kind == 1)
{
/* Julian day, never counting 29 February. */
if (r->day <= 59)
{
doy = r->day - 1;
}
else
{
doy = r->day - (tz_is_leap(year) ? 0 : 1);
}
}
else
{
doy = r->day; /* zero-based day, leap day counted */
}
return days_from_civil(year, 1, 1) + doy;
}
}
/*
* tz_rule_contains: is DST in effect at the given LOCAL civil time
* (seconds, in the no-offset frame all TZ math uses)?
*
* The two transitions are computed for the civil year that contains
* local_sec. When the start rule comes before the end rule in the year the
* DST interval is [start, end); when it comes after (southern hemisphere,
* DST spanning the new year) the interval wraps, so local_sec before the
* year's end transition or at/after its start transition is daylight time.
*/
// NOLINTBEGIN(bugprone-easily-swappable-parameters,bugprone-reserved-identifier)
int
tz_rule_contains(const struct tz_state *s, long long local_sec)
{
int y;
int m;
int d;
long long start_day;
long long end_day;
long long start_sec;
long long end_sec;
if (!s->has_dst)
{
return 0;
}
civil_from_days(floor_div(local_sec, 86400), &y, &m, &d);
(void)m;
(void)d;
start_day = tz_rule_day(y, &s->start);
end_day = tz_rule_day(y, &s->end);
start_sec = start_day * 86400 + s->start.time;
end_sec = end_day * 86400 + s->end.time;
if (start_day <= end_day)
{
return local_sec >= start_sec && local_sec < end_sec;
}
return local_sec >= start_sec || local_sec < end_sec;
}
/*
* tz_offset_for_local: the offset (and DST flag) for a local civil time.
* A positive isdst_hint forces daylight time, a zero hint forces standard
* time, and a negative hint ("unknown", as mktime receives from callers
* that do not know) is resolved by the zone rules.
*/
long
tz_offset_for_local(const struct tz_state *s, long long local_sec, int isdst_hint, int *isdst_out)
{
int isdst;
long off;
if (s->has_dst && isdst_hint > 0)
{
isdst = 1;
}
else if (s->has_dst && isdst_hint < 0)
{
isdst = tz_rule_contains(s, local_sec);
}
else
{
isdst = 0;
}
off = isdst ? s->dst_off : s->std_off;
if (isdst_out != NULL)
{
*isdst_out = isdst;
}
return off;
}
/*
* tz_offset_at_utc: the offset (and DST flag) in effect at the given UTC
* instant, found by the standard fixed-point iteration — assume standard
* time, convert to a local civil guess, consult the rules, and re-derive
* the offset once (DST transitions move the local time by at most one
* hour, so a second pass settles it).
*/
long
tz_offset_at_utc(const struct tz_state *s, long long utc_sec, int *isdst_out)
{
long off = s->std_off;
int isdst = 0;
if (s->has_dst)
{
isdst = tz_rule_contains(s, utc_sec + off);
off = isdst ? s->dst_off : s->std_off;
isdst = tz_rule_contains(s, utc_sec + off);
off = isdst ? s->dst_off : s->std_off;
}
if (isdst_out != NULL)
{
*isdst_out = isdst;
}
return off;
}
// NOLINTEND(bugprone-reserved-identifier)
/*
* __tzset_lazy: (re)parse TZ into the static state, but only once per
* tzset() call. Callers may run before the environment is installed, so
* getenv returning nothing is simply the UTC default.
*/
// NOLINTBEGIN(bugprone-reserved-identifier)
void
__tzset_lazy(void)
{
const char *tz;
if (tz_parsed)
{
return;
}
tz = getenv("TZ");
if (tz == NULL || tz[0] == '\0' || !tz_parse(tz, &tz_state))
{
memset(&tz_state, 0, sizeof tz_state);
strcpy(tz_state.std_name, "UTC");
}
tz_parsed = 1;
#if VLIBC_LEVEL_GE(2)
tzname[0] = tz_state.std_name;
tzname[1] = tz_state.dst_name;
daylight = tz_state.has_dst;
timezone = -tz_state.std_off; /* POSIX: seconds WEST of UTC */
#endif
}
const struct tz_state *
__tz_state(void)
{
__tzset_lazy();
return &tz_state;
}
// NOLINTEND(bugprone-reserved-identifier)
/*
* tzset: re-read TZ on the next access. The parse itself is deferred to
* __tzset_lazy so that a tzset() with no following time query is cheap.
*/
void
tzset(void)
{
tz_parsed = 0;
__tzset_lazy();
}
struct tm *
gmtime_r(const time_t *timer, struct tm *result)
{
__fill_tm((long long)*timer, 0, 0, "UTC", result);
return result;
}
struct tm *
localtime_r(const time_t *timer, struct tm *result)
{
const struct tz_state *s;
long off;
int isdst;
__tzset_lazy();
s = __tz_state();
off = tz_offset_at_utc(s, (long long)*timer, &isdst);
__fill_tm((long long)*timer + off, off, isdst, isdst ? s->dst_name : s->std_name, result);
return result;
}
static struct tm tz_static_tm;
struct tm *
gmtime(const time_t *timer)
{
return gmtime_r(timer, &tz_static_tm);
}
struct tm *
localtime(const time_t *timer)
{
return localtime_r(timer, &tz_static_tm);
}
#if VLIBC_LEVEL_GE(2)
/* POSIX timezone globals, kept in sync by tzset()/__tzset_lazy(). */
char *tzname[2];
int daylight;
long timezone;
#endif /* VLIBC_LEVEL_GE(2) */
+384
View File
@@ -0,0 +1,384 @@
/*
* vlibc — time library test (todo 26, slice S3).
*
* Exercises the full time stack end to end against golden values verified
* against glibc (C locale, TZ=UTC):
*
* 1. strftime: every POSIX conversion on a fixed 2024-03-15 14:30:45
* (Friday, yday 74) struct, plus %e space-padding and %z rendering of
* a negative offset.
* 2. asctime/ctime fixed-format "Www Mmm dd hh:mm:ss yyyy\n" rendering.
* 3. mktime/gmtime/timegm round-trips; localtime under TZ=UTC.
* 4. nanosleep and (level 2) usleep, strptime round-trip, and the POSIX
* interval-timer syscall wrappers.
*
* The -f mode exercises the failure shapes (buffer overflow, year outside
* [1000,9999], unmatchable strptime) and leaves via a raw SYS_exit_group so
* no host cleanup runs afterwards (vlibc's errno slot collides with the
* host TCB, so errno is never read here).
*
* Host-header-free: everything comes from vlibc's public headers via
* -Iinclude; diagnostics go through raw SYS_write; environ is defined in
* this TU (the host startup does not install the vlibc environment) and
* pointed at the envp main receives.
*/
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define TEST_SYS_WRITE 1
#define TEST_SYS_EXIT_GROUP 231
char **environ;
char **__environ; // NOLINT(bugprone-reserved-identifier)
#if VLIBC_LEVEL_GE(2)
/* Level-2 timer ABI mirror (see src/time/timer.c). */
struct itimerspec
{
struct timespec it_interval;
struct timespec it_value;
};
#endif
static int failures;
static long
raw3(long n, long a, long b, long c)
{
unsigned long ret;
__asm__ volatile("syscall"
: "=a"(ret)
: "a"(n), "D"(a), "S"(b), "d"(c)
: "rcx", "r11", "memory");
return (long)ret;
}
static long
raw1(long n, long a)
{
unsigned long ret;
__asm__ volatile("syscall" : "=a"(ret) : "a"(n), "D"(a) : "rcx", "r11", "memory");
return (long)ret;
}
static void
say(const char *s)
{
size_t n = 0;
while (s[n] != '\0')
{
n++;
}
raw3(TEST_SYS_WRITE, 1, (long)s, (long)n);
}
static void
saynum(long v)
{
char buf[24];
size_t i = sizeof buf;
if (v == 0)
{
say("0");
return;
}
if (v < 0)
{
say("-");
v = -v;
}
while (v > 0)
{
buf[--i] = (char)('0' + v % 10);
v /= 10;
}
raw3(TEST_SYS_WRITE, 1, (long)(buf + i), (long)(sizeof buf - i));
}
static void
check(int cond, const char *what)
{
if (cond)
{
say("ok ");
}
else
{
say("FAIL ");
failures++;
}
say(what);
say("\n");
}
/* The canonical 2024-03-15 14:30:45 struct, normalized via mktime in UTC. */
static void
make_tm(struct tm *t)
{
memset(t, 0, sizeof *t);
t->tm_year = 2024 - 1900;
t->tm_mon = 2;
t->tm_mday = 15;
t->tm_hour = 14;
t->tm_min = 30;
t->tm_sec = 45;
t->tm_isdst = -1;
}
static void
strftime_scenario(void)
{
static const struct
{
const char *fmt;
const char *want;
} gold[] = {
{"%a", "Fri"},
{"%A", "Friday"},
{"%b", "Mar"},
{"%B", "March"},
{"%c", "Fri Mar 15 14:30:45 2024"},
{"%C", "20"},
{"%d", "15"},
{"%D", "03/15/24"},
{"%e", "15"},
{"%F", "2024-03-15"},
{"%g", "24"},
{"%G", "2024"},
{"%h", "Mar"},
{"%H", "14"},
{"%I", "02"},
{"%j", "075"},
{"%m", "03"},
{"%M", "30"},
{"%p", "PM"},
{"%r", "02:30:45 PM"},
{"%R", "14:30"},
{"%S", "45"},
{"%T", "14:30:45"},
{"%u", "5"},
{"%U", "10"},
{"%V", "11"},
{"%w", "5"},
{"%W", "11"},
{"%x", "03/15/24"},
{"%X", "14:30:45"},
{"%y", "24"},
{"%Y", "2024"},
{"%z", "+0000"},
{"%Z", "UTC"},
{"%%", "%"},
{"%Y-%m-%d %H:%M:%S", "2024-03-15 14:30:45"},
};
struct tm t;
struct tm t5;
char buf[128];
size_t i;
time_t e;
size_t want_len;
make_tm(&t);
e = mktime(&t);
check(t.tm_wday == 5 && t.tm_yday == 74, "mktime normalized 2024-03-15 -> Fri/yday 74");
check(t.tm_gmtoff == 0 && t.tm_isdst == 0, "mktime under TZ=UTC keeps gmtoff 0 isdst 0");
for (i = 0; i < sizeof gold / sizeof gold[0]; i++)
{
size_t n = strftime(buf, sizeof buf, gold[i].fmt, &t);
want_len = 0;
while (gold[i].want[want_len] != '\0')
{
want_len++;
}
if (strcmp(buf, gold[i].want) != 0 || n != want_len)
{
check(0, gold[i].fmt);
}
else
{
check(1, gold[i].fmt);
}
}
t5 = t;
t5.tm_mday = 5;
check(strftime(buf, sizeof buf, "%e", &t5) == 2 && strcmp(buf, " 5") == 0,
"%e space-pads single digits");
t.tm_gmtoff = -18000;
check(strftime(buf, sizeof buf, "%z", &t) == 5 && strcmp(buf, "-0500") == 0,
"%z renders negative gmtoff");
check(strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", &t) == 19 &&
strcmp(buf, "2024-03-15 14:30:45") == 0,
"combined format golden");
(void)e;
}
static void
roundtrip_scenario(void)
{
struct tm tmv;
struct timespec ts;
time_t e0 = 1700000000;
time_t t0 = 0;
struct tm gtm;
time_t back;
/* ctime golden (UTC): same text as asctime of the fixed time. */
back = 0;
(void)back;
tmv.tm_year = 124;
tmv.tm_mon = 2;
tmv.tm_mday = 15;
tmv.tm_hour = 14;
tmv.tm_min = 30;
tmv.tm_sec = 45;
tmv.tm_isdst = -1;
back = mktime(&tmv);
check(strcmp(asctime(&tmv), "Fri Mar 15 14:30:45 2024\n") == 0, "asctime golden");
check(strcmp(ctime(&back), "Fri Mar 15 14:30:45 2024\n") == 0, "ctime golden");
/* mktime round-trips gmtime. */
gtm = *gmtime(&e0);
back = mktime(&gtm);
check(back == e0, "mktime round-trips gmtime(1700000000)");
localtime_r(&t0, &tmv);
check(tmv.tm_gmtoff == 0 && tmv.tm_isdst == 0, "localtime(0) under TZ=UTC");
ts.tv_sec = 0;
ts.tv_nsec = 1000000L;
check(nanosleep(&ts, NULL) == 0, "nanosleep 1ms");
#if VLIBC_LEVEL_GE(2)
/* timegm round-trips gmtime without any timezone state. */
gtm = *gmtime(&e0);
check(timegm(&gtm) == e0, "timegm round-trips gmtime(1700000000)");
/* strptime parses what strftime emitted for the fixed time. */
{
char sbuf[64];
char abuf[26];
struct tm pt;
struct tm ft;
time_t fe;
make_tm(&ft);
fe = mktime(&ft);
check(fe != (time_t)-1, "mktime on fixed time succeeds");
strftime(sbuf, sizeof sbuf, "%Y-%m-%d %H:%M:%S", &ft);
check(strcmp(sbuf, "2024-03-15 14:30:45") == 0, "strftime source for round-trip");
check(strptime(sbuf, "%Y-%m-%d %H:%M:%S", &pt) != NULL, "strptime parses golden");
check(timegm(&pt) == fe, "strptime -> timegm round-trip equals mktime");
check(strcmp(asctime_r(&ft, abuf), "Fri Mar 15 14:30:45 2024\n") == 0, "asctime_r golden");
check(strcmp(ctime_r(&fe, abuf), "Fri Mar 15 14:30:45 2024\n") == 0, "ctime_r golden");
check(usleep(1000) == 0, "usleep 1000us");
}
/* POSIX interval timers (S1 wrappers). */
{
struct itimerspec itv;
struct itimerspec got;
timer_t tid = 0;
check(timer_create(CLOCK_REALTIME, NULL, &tid) == 0, "timer_create");
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_nsec = 0;
itv.it_value.tv_sec = 0;
itv.it_value.tv_nsec = 10000000L;
check(timer_settime(tid, 0, &itv, NULL) == 0, "timer_settime 10ms");
got.it_value.tv_sec = 0;
got.it_value.tv_nsec = 0;
check(timer_gettime(tid, &got) == 0 &&
(got.it_value.tv_sec > 0 || got.it_value.tv_nsec > 0),
"timer_gettime reports pending expiry");
check(timer_delete(tid) == 0, "timer_delete");
}
#endif
}
static void
failure_scenarios(void)
{
struct tm t;
struct tm y10k;
char buf[4];
make_tm(&t);
mktime(&t);
check(strftime(buf, sizeof buf, "%Y-%m-%d", &t) == 0, "-f strftime overflow returns 0");
memset(&y10k, 0, sizeof y10k);
y10k.tm_year = 10000 - 1900;
y10k.tm_mon = 0;
y10k.tm_mday = 1;
y10k.tm_hour = 0;
y10k.tm_min = 0;
y10k.tm_sec = 0;
y10k.tm_wday = 0;
check(asctime(&y10k) == NULL, "-f asctime year 10000 returns NULL");
#if VLIBC_LEVEL_GE(2)
{
struct tm pt;
check(strptime("nope", "%Y", &pt) == NULL, "-f strptime mismatch returns NULL");
}
#endif
}
int
main(int argc, char **argv, char **envp)
{
int fail_mode = argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f';
environ = envp;
__environ = envp;
setenv("TZ", "UTC", 1);
tzset();
if (fail_mode)
{
failure_scenarios();
if (failures != 0)
{
say("FAILURES: ");
saynum(failures);
say("\n");
}
else
{
say("all time -f tests passed\n");
}
raw1(TEST_SYS_EXIT_GROUP, failures != 0);
/* not reached */
return failures != 0;
}
strftime_scenario();
roundtrip_scenario();
if (failures != 0)
{
say("FAILURES: ");
saynum(failures);
say("\n");
return 1;
}
say("all time tests passed\n");
return 0;
}