feat(time): time/clock/mktime/strftime/nanosleep
This commit is contained in:
+255
@@ -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 */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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) */
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
@@ -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) */
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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) */
|
||||||
@@ -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(>m);
|
||||||
|
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(>m) == 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user