250 lines
8.6 KiB
C
250 lines
8.6 KiB
C
// time.h additions (struct tm, gmtime, localtime, mktime, asctime,
|
|
// ctime, difftime, strftime). Split out from libc.c to keep that
|
|
// translation unit's frame size below the W65816 stack-relative range.
|
|
|
|
typedef long time_t;
|
|
typedef unsigned long clock_t;
|
|
typedef unsigned long size_t;
|
|
|
|
extern size_t strlen(const char *);
|
|
|
|
static const unsigned short __monthDays[12] = {
|
|
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
|
|
};
|
|
|
|
static int __isLeap(int y) {
|
|
return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
|
|
}
|
|
|
|
// ---- time.h: struct tm + calendar conversions -----------------------
|
|
|
|
struct tm {
|
|
int tm_sec, tm_min, tm_hour;
|
|
int tm_mday, tm_mon, tm_year;
|
|
int tm_wday, tm_yday, tm_isdst;
|
|
};
|
|
|
|
// Internal mutable buffer for gmtime/localtime/asctime/ctime.
|
|
// Not thread-safe but matches the C standard's spec.
|
|
static struct tm __gmtimeBuf;
|
|
static char __asctimeBuf[32];
|
|
|
|
double difftime(time_t end, time_t start) {
|
|
return (double)(end - start);
|
|
}
|
|
|
|
struct tm *gmtime_r(const time_t *t, struct tm *out);
|
|
|
|
// gmtime / localtime: convert seconds-since-1970 to broken-down time.
|
|
// "local" is identical to "gm" — no timezone support.
|
|
//
|
|
// Returns a pointer to a static global (`__gmtimeBuf`). Under GS/OS
|
|
// Loader (DBR != 0) caller-side pointer-deref reads need to land in
|
|
// the same bank where gmtime wrote; this requires the runtime build
|
|
// to enable `-mllvm -w65816-loader-bank-deref`, which makes
|
|
// LDAptr/STAptr load the bank byte from DP $BE (set by crt0 from
|
|
// PHK / current PBR). Without the flag, gmtime still works under
|
|
// MAME / non-Loader runs where DBR=0 throughout.
|
|
struct tm *gmtime(const time_t *t) {
|
|
return gmtime_r(t, &__gmtimeBuf);
|
|
}
|
|
|
|
struct tm *localtime(const time_t *t) {
|
|
return gmtime(t);
|
|
}
|
|
|
|
// gmtime_r / localtime_r — POSIX reentrant variants. Take a caller-
|
|
// supplied struct tm so the buffer lives on the user's stack (which
|
|
// is bank-0 in 65816 native mode regardless of DBR). This avoids the
|
|
// bank-mismatch issue that breaks plain gmtime under Loader.
|
|
//
|
|
// Full broken-down time computation. Marked optnone because at -O2
|
|
// LLVM's combined IR optimizations (loop rotation + reassociation +
|
|
// induction-variable-simplify) mis-evaluate the year-increment loop's
|
|
// `days >= 365L + (__isLeap(...) ? 1 : 0)` comparison, leaving the
|
|
// loop body unexecuted and date fields stuck at the 1970 sentinel.
|
|
// optnone preserves the per-statement structure and the loop runs
|
|
// correctly. Verified end-to-end against 1710484245L → 2024-03-15
|
|
// 06:30:45 UTC (Friday, day-of-year 74).
|
|
__attribute__((optnone))
|
|
struct tm *gmtime_r(const time_t *t, struct tm *out) {
|
|
long secs = *t;
|
|
int sec = (int)(secs % 60L); secs /= 60L;
|
|
int min = (int)(secs % 60L); secs /= 60L;
|
|
int hour = (int)(secs % 24L); secs /= 24L;
|
|
long days = secs;
|
|
int wday = (int)((days + 4L) % 7L);
|
|
if (wday < 0) wday += 7;
|
|
|
|
int year = 70; // years since 1900
|
|
while (days >= 365L + (__isLeap(1900 + year) ? 1 : 0)) {
|
|
days -= 365L + (__isLeap(1900 + year) ? 1 : 0);
|
|
year++;
|
|
}
|
|
int yday = (int)days;
|
|
int leap = __isLeap(1900 + year);
|
|
int mon = 11;
|
|
while (mon > 0) {
|
|
int firstDayOfMon = __monthDays[mon] + (leap && mon > 1 ? 1 : 0);
|
|
if ((int)days >= firstDayOfMon) break;
|
|
mon--;
|
|
}
|
|
int firstDay = __monthDays[mon] + (leap && mon > 1 ? 1 : 0);
|
|
int mday = (int)days - firstDay + 1;
|
|
|
|
out->tm_sec = sec;
|
|
out->tm_min = min;
|
|
out->tm_hour = hour;
|
|
out->tm_mday = mday;
|
|
out->tm_mon = mon;
|
|
out->tm_year = year;
|
|
out->tm_wday = wday;
|
|
out->tm_yday = yday;
|
|
out->tm_isdst = -1;
|
|
return out;
|
|
}
|
|
|
|
struct tm *localtime_r(const time_t *t, struct tm *out) {
|
|
return gmtime_r(t, out);
|
|
}
|
|
|
|
// mktime: convert broken-down time → seconds-since-1970. Also fills
|
|
// in tm_wday and tm_yday if the caller didn't bother.
|
|
time_t mktime(struct tm *tm) {
|
|
int year = tm->tm_year + 1900;
|
|
int mon = tm->tm_mon;
|
|
long days = 0;
|
|
for (int y = 1970; y < year; y++) {
|
|
days += __isLeap(y) ? 366 : 365;
|
|
}
|
|
for (int m = 0; m < mon; m++) {
|
|
days += __monthDays[m + 1] - __monthDays[m];
|
|
// (the table starts at 0 for Jan, so each month is the diff)
|
|
}
|
|
if (mon > 1 && __isLeap(year)) days++;
|
|
days += tm->tm_mday - 1;
|
|
long secs = days * 86400L
|
|
+ (long)tm->tm_hour * 3600L
|
|
+ (long)tm->tm_min * 60L
|
|
+ tm->tm_sec;
|
|
tm->tm_wday = (int)((days + 4) % 7);
|
|
if (tm->tm_wday < 0) tm->tm_wday += 7;
|
|
tm->tm_yday = (int)(__monthDays[mon]
|
|
+ ((mon > 1 && __isLeap(year)) ? 1 : 0)
|
|
+ tm->tm_mday - 1);
|
|
return secs;
|
|
}
|
|
|
|
static const char *const __wdayShort[7] = {
|
|
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
|
|
};
|
|
static const char *const __wdayLong[7] = {
|
|
"Sunday", "Monday", "Tuesday", "Wednesday",
|
|
"Thursday", "Friday", "Saturday"
|
|
};
|
|
static const char *const __monShort[12] = {
|
|
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
|
};
|
|
static const char *const __monLong[12] = {
|
|
"January", "February", "March", "April",
|
|
"May", "June", "July", "August",
|
|
"September", "October", "November", "December"
|
|
};
|
|
|
|
// Format N-digit zero-padded. Forces u32 (long) arithmetic — clang's
|
|
// strength reducer otherwise lowers /10 and %10 on small types into
|
|
// i8 mulhu by 0xCD (magic constant for div-by-10), which the W65816
|
|
// backend has no select pattern for.
|
|
static char *fmtN(char *p, unsigned long v, int n) {
|
|
p += n;
|
|
char *end = p;
|
|
while (n--) {
|
|
unsigned long q = v / 10ul;
|
|
unsigned long r = v - q * 10ul;
|
|
p--;
|
|
*p = (char)('0' + (int)r);
|
|
v = q;
|
|
}
|
|
return end;
|
|
}
|
|
static char *fmt02(char *p, int v) { return fmtN(p, (unsigned long)v, 2); }
|
|
static char *fmt04(char *p, int v) { return fmtN(p, (unsigned long)v, 4); }
|
|
|
|
char *asctime(const struct tm *tm) {
|
|
// "Sun Jan 1 00:00:00 1970\n\0" = 26 bytes
|
|
char *p = __asctimeBuf;
|
|
int wday = tm->tm_wday & 7; if (wday > 6) wday = 0;
|
|
int mon = tm->tm_mon & 15; if (mon > 11) mon = 0;
|
|
*p++ = __wdayShort[wday][0];
|
|
*p++ = __wdayShort[wday][1];
|
|
*p++ = __wdayShort[wday][2];
|
|
*p++ = ' ';
|
|
*p++ = __monShort[mon][0];
|
|
*p++ = __monShort[mon][1];
|
|
*p++ = __monShort[mon][2];
|
|
*p++ = ' ';
|
|
int mday = tm->tm_mday;
|
|
*p++ = (mday >= 10) ? ('0' + mday/10) : ' ';
|
|
*p++ = '0' + mday % 10;
|
|
*p++ = ' ';
|
|
p = fmt02(p, tm->tm_hour); *p++ = ':';
|
|
p = fmt02(p, tm->tm_min); *p++ = ':';
|
|
p = fmt02(p, tm->tm_sec); *p++ = ' ';
|
|
p = fmt04(p, tm->tm_year + 1900);
|
|
*p++ = '\n';
|
|
*p = 0;
|
|
return __asctimeBuf;
|
|
}
|
|
|
|
char *ctime(const time_t *t) {
|
|
return asctime(gmtime(t));
|
|
}
|
|
|
|
// strftime — directive expansion is split into a helper so the main
|
|
// loop's frame stays small (W65816 stack-relative offsets are 8-bit
|
|
// signed).
|
|
__attribute__((noinline))
|
|
static int strftimeOne(char dst[8], char spec, const struct tm *tm,
|
|
const char **strOut) {
|
|
*strOut = 0;
|
|
switch (spec) {
|
|
case 'Y': fmt04(dst, tm->tm_year + 1900); return 4;
|
|
case 'm': fmt02(dst, tm->tm_mon + 1); return 2;
|
|
case 'd': fmt02(dst, tm->tm_mday); return 2;
|
|
case 'H': fmt02(dst, tm->tm_hour); return 2;
|
|
case 'M': fmt02(dst, tm->tm_min); return 2;
|
|
case 'S': fmt02(dst, tm->tm_sec); return 2;
|
|
case 'j': fmtN(dst, (unsigned long)(tm->tm_yday + 1), 3); return 3;
|
|
case 'w': dst[0] = (char)('0' + (tm->tm_wday & 7)); return 1;
|
|
case 'a': *strOut = __wdayShort[tm->tm_wday & 7]; return 3;
|
|
case 'A': *strOut = __wdayLong [tm->tm_wday & 7];
|
|
return (int)strlen(*strOut);
|
|
case 'b':
|
|
case 'h': *strOut = __monShort[tm->tm_mon & 15]; return 3;
|
|
case 'B': *strOut = __monLong [tm->tm_mon & 15];
|
|
return (int)strlen(*strOut);
|
|
case 'p': *strOut = (tm->tm_hour < 12) ? "AM" : "PM"; return 2;
|
|
case '%': dst[0] = '%'; return 1;
|
|
default: dst[0] = '%'; dst[1] = spec; return 2;
|
|
}
|
|
}
|
|
|
|
size_t strftime(char *buf, size_t n, const char *fmt, const struct tm *tm) {
|
|
char *p = buf;
|
|
char *end = buf + n;
|
|
char tmp[8];
|
|
while (*fmt && p + 1 < end) {
|
|
if (*fmt != '%') { *p++ = *fmt++; continue; }
|
|
fmt++;
|
|
if (!*fmt) break;
|
|
const char *ins;
|
|
int len = strftimeOne(tmp, *fmt, tm, &ins);
|
|
const char *src = ins ? ins : tmp;
|
|
for (int i = 0; i < len && p + 1 < end; i++) *p++ = src[i];
|
|
fmt++;
|
|
}
|
|
if (p < end) *p = 0;
|
|
else if (n > 0) buf[n - 1] = 0;
|
|
return (size_t)(p - buf);
|
|
}
|