commit 4dc7e1ae7b8c34bdb9847e902a6e2701a9bd43c3
parent 1c2ad3584e9473c7327a908b2435fdcdbe535777
Author: Roberto E. Vargas Caballero <k0ga@shike2.net>
Date: Sat, 18 Apr 2026 20:01:12 +0200
libc/time: Rework the TZ parser
Many bugs were found and it made it more similar to the POSIX
specification.
Diffstat:
4 files changed, 94 insertions(+), 79 deletions(-)
diff --git a/src/libc/arch/posix/_tzone.c b/src/libc/arch/posix/_tzone.c
@@ -1,3 +1,4 @@
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -21,11 +22,10 @@ int _daylight;
char *_tzname[2];
long _timezone, _dstzone;
-static char st[TOKENSIZ], ds[TOKENSIZ], tokstr[TOKENSIZ];
static int tok;
+static char tokstr[TOKENSIZ];
-static int start, end;
-static int julian;
+static int julian, start, end;
static int
next(char *str)
@@ -89,8 +89,6 @@ num(int max)
{
int n;
- if (tok == EOS)
- return 0;
if (tok != NUM)
return -1;
n = atoi(tokstr);
@@ -102,21 +100,18 @@ num(int max)
static long
offset(void)
{
- int sign = 1;
- int n;
long off;
+ int n, sign = 1;
if (tok == EOS)
return -1;
switch (tok) {
- case '+':
- sign = -1;
case '-':
+ sign = -1;
+ case '+':
next(NULL);
break;
- default:
- return -1;
}
if ((n = num(24)) < 0)
@@ -127,16 +122,14 @@ offset(void)
goto ret;
if (!accept(':'))
- return -1;
+ goto ret;
if ((n = num(60)) < 0)
return -1;
off += n * SECMIN;
next(NULL);
- if (tok == EOS)
- goto ret;
if (!accept(':'))
- return -1;
+ goto ret;
if ((n = num(60)) < 0)
return -1;
off += n;
@@ -150,14 +143,16 @@ static int
std(void)
{
long off;
+ char *name;
- if (tok != STR)
+ if ((name = _gmtoff(tokstr)) == NULL)
return 0;
- strcpy(st, tokstr);
- next(NULL);
+ next(NULL);
if ((off = offset()) == -1)
return 0;
+
+ _tzname[0] = name;
_timezone = off;
return 1;
@@ -167,12 +162,13 @@ static int
dst(void)
{
long off;
+ char *name;
- if (tok != STR)
+ if ((name = _gmtoff(tokstr)) == NULL)
return 0;
- strcpy(ds, tokstr);
- next(NULL);
+ _tzname[1] = name;
+ next(NULL);
if ((off = offset()) == -1)
_dstzone = _timezone + SECHOUR;
else
@@ -214,8 +210,6 @@ yday(void)
static int
rule(void)
{
- if (tok == EOS)
- return 0;
if (!accept(','))
return 0;
if ((start = yday()) == -1)
@@ -230,37 +224,51 @@ void
_tzset(void)
{
static char *tz;
+ static char cache[80];
char *s = getenv("TZ");
- if (s && tz && strcmp(s, tz) == 0)
- return;
- tz = s;
-
- _daylight = 0;
- _tzname[1] = _tzname[0] = "UTC";
- _timezone = _dstzone = -1;
+ if (s) {
+ if (tz && strcmp(s, tz) == 0)
+ return;
+ snprintf(cache, sizeof(cache), "%s", s);
+ tz = cache;
+ }
start = end = -1;
- julian = 0;
- if (!tz)
+ if (!s)
goto adjust;
- next(tz);
+
+ if (next(s) == EOS)
+ goto error;
if (!std())
+ goto error;
+ if (tok == EOS)
goto adjust;
+
if (!dst())
+ goto error;
+ if (tok == EOS)
goto adjust;
+
if (!rule())
- goto adjust;
+ goto error;
adjust:
- if (!_daylight)
+ if (!_daylight) {
_tzname[1] = _tzname[0];
- if (_timezone == -1)
- _timezone = _gmtoff(_tzname[0]);
- if (_dstzone == -1)
- _dstzone = _gmtoff(_tzname[1]);
+ _dstzone = _timezone;
+ }
+
+ return;
+
+error:
+ _daylight = 0;
+ _tzname[1] = _tzname[0] = "UTC";
+ _timezone = _dstzone = 0;
+ start = end = -1;
+ julian = 0;
}
int
@@ -268,6 +276,9 @@ _isdst(struct tm *tm)
{
int yday;
+ if (!_daylight)
+ return 0;
+
yday = tm->tm_yday;
if (julian && yday+1 < 60 || FEBDAYS(tm->tm_year) < 29)
diff --git a/src/libc/libc.h b/src/libc/libc.h
@@ -12,15 +12,6 @@ FILE *_fpopen(const char * restrict, const char *restrict,
#endif
#ifdef _TIME_H
-enum {
- SUN,
- MON,
- TUE,
- WED,
- THU,
- FRI,
- SAT
-};
#define JAN 0
#define FEB 1
@@ -33,18 +24,30 @@ enum {
#define SECHOUR (60 * SECMIN) /* 3600 */
#define SECDAY (24 * SECHOUR) /* 86400 */
+enum {
+ SUN,
+ MON,
+ TUE,
+ WED,
+ THU,
+ FRI,
+ SAT
+};
+
+struct tzone;
+
/* _tzone.c variables */
-extern time_t _tzstdoff, _tzdstoff;
-extern time_t _tzstart, _tzend;
+extern int _daylight;
extern char *_tzname[2];
-extern int _tzjulian;
+extern long _timezone, _dstzone;
extern int _daysmon[12];
-long _gmtoff(char *);
void _tzset(void);
+int _isdst(struct tm *);
int _daysyear(int);
int _newyear(int);
+char *_gmtoff(char *);
#endif
extern unsigned _exitn;
diff --git a/src/libc/time/gentz b/src/libc/time/gentz
@@ -1,39 +1,40 @@
#!/bin/sh
-cat <<'EOF'
+cat <<EOF
+#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "../libc.h"
-struct tzone {
- char *name;
- long gmtoff;
-};
-
-static struct tzone tzones[];
-
-long
-_gmtoff(char *tz)
+`
+awk '{
+ zones[$1] = 1
+ if (length($1) > max)
+ max = length($1)
+ nr++
+}
+END {
+ printf("#define MAX %d\n", max+1);
+ printf("#define NR %d\n", nr);
+ print("static char tzones[NR][MAX] = {");
+ for (z in zones)
+ printf("\t\\"%s\\",\n", z) | "sort"
+ close("sort")
+ print "};\n"
+}' "$@"`
+
+static int
+cmp(const void *ps1, const void *ps2)
{
- struct tzone *t;
+ const char *s1 = ps1, *s2 = ps2;
- for (t = tzones; t->name; t++) {
- if (!strcmp(t->name, tz))
- return t->gmtoff;
- }
- return 0;
+ return strcmp(s1, s2);
}
-EOF
-
-awk '
-BEGIN {print "static struct tzone tzones[] = {" }
+char *
+_gmtoff(char *tz)
{
- split($2,a,":")
- min = a[1] * 60
- min = min + a[2]
- sec = min * 60
- printf "\t{\"%s\", %d},\n", $1, sec
+ return bsearch(tz, tzones, NR, MAX, cmp);
}
-END { print "\t{0,\t0}\n};\n" }' "$@"
+EOF
diff --git a/src/libc/time/localtime.c b/src/libc/time/localtime.c
@@ -27,7 +27,7 @@ localtime(const time_t *timep)
name = _tzname[0];
}
- t += off;
+ t -= off;
tm = gmtime(&t);
tm->tm_zone = name;
tm->tm_isdst = dst;