libc: new date apis

The current date and time APIs on Plan 9 are not good. They're
inflexible, non-threadsafe, and don't expose timezone information.

This commit adds new time APIs that allow parsing arbitrary
dates, work from multiple threads, and can handle timezones
effectively.
This commit is contained in:
Ori Bernstein 2020-08-09 18:58:44 -07:00
parent 3ba1d83d20
commit 56e869ac70
6 changed files with 1329 additions and 488 deletions

View file

@ -1,301 +1,39 @@
/*
* This routine converts time as follows.
* The epoch is 0000 Jan 1 1970 GMT.
* The argument time is in seconds since then.
* The localtime(t) entry returns a pointer to an array
* containing
*
* seconds (0-59)
* minutes (0-59)
* hours (0-23)
* day of month (1-31)
* month (0-11)
* year-1970
* weekday (0-6, Sun is 0)
* day of the year
* daylight savings flag
*
* The routine gets the daylight savings time from the environment.
*
* asctime(tvec))
* where tvec is produced by localtime
* returns a ptr to a character string
* that has the ascii time in the form
*
* \\
* Thu Jan 01 00:00:00 GMT 1970n0
* 012345678901234567890123456789
* 0 1 2
*
* ctime(t) just calls localtime, then asctime.
*/
#include <u.h>
#include <libc.h>
static char dmsize[12] =
{
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
/*
* The following table is used for 1974 and 1975 and
* gives the day number of the first day after the Sunday of the
* change.
*/
static int dysize(int);
static void ct_numb(char*, int);
#define TZSIZE 150
static void readtimezone(void);
static int rd_name(char**, char*);
static int rd_long(char**, long*);
static
struct
{
char stname[4];
char dlname[4];
long stdiff;
long dldiff;
long dlpairs[TZSIZE];
} timezone;
char*
ctime(long t)
{
return asctime(localtime(t));
}
Tm*
localtime(long tim)
{
Tm *ct;
long t, *p;
int dlflag;
static Tm tm;
Tzone *tz;
if(timezone.stname[0] == 0)
readtimezone();
t = tim + timezone.stdiff;
dlflag = 0;
for(p = timezone.dlpairs; *p; p += 2)
if(t >= p[0])
if(t < p[1]) {
t = tim + timezone.dldiff;
dlflag++;
break;
}
ct = gmtime(t);
if(dlflag){
strcpy(ct->zone, timezone.dlname);
ct->tzoff = timezone.dldiff;
} else {
strcpy(ct->zone, timezone.stname);
ct->tzoff = timezone.stdiff;
}
return ct;
/*
* We have no way to report errors,
* so we just ignore them here.
*/
tz = tzload("local");
tmtime(&tm, tim, tz);
return &tm;
}
Tm*
gmtime(long tim)
gmtime(long abs)
{
int d0, d1;
long hms, day;
static Tm xtime;
/*
* break initial number into days
*/
hms = tim % 86400L;
day = tim / 86400L;
if(hms < 0) {
hms += 86400L;
day -= 1;
}
/*
* generate hours:minutes:seconds
*/
xtime.sec = hms % 60;
d1 = hms / 60;
xtime.min = d1 % 60;
d1 /= 60;
xtime.hour = d1;
/*
* day is the day number.
* generate day of the week.
* The addend is 4 mod 7 (1/1/1970 was Thursday)
*/
xtime.wday = (day + 7340036L) % 7;
/*
* year number
*/
if(day >= 0)
for(d1 = 1970; day >= dysize(d1); d1++)
day -= dysize(d1);
else
for (d1 = 1970; day < 0; d1--)
day += dysize(d1-1);
xtime.year = d1-1900;
xtime.yday = d0 = day;
/*
* generate month
*/
if(dysize(d1) == 366)
dmsize[1] = 29;
for(d1 = 0; d0 >= dmsize[d1]; d1++)
d0 -= dmsize[d1];
dmsize[1] = 28;
xtime.mday = d0 + 1;
xtime.mon = d1;
strcpy(xtime.zone, "GMT");
return &xtime;
static Tm tm;
return tmtime(&tm, abs, nil);
}
char*
asctime(Tm *t)
ctime(long abs)
{
char *ncp;
static char cbuf[30];
Tzone *tz;
Tm tm;
strcpy(cbuf, "Thu Jan 01 00:00:00 GMT 1970\n");
ncp = &"SunMonTueWedThuFriSat"[t->wday*3];
cbuf[0] = *ncp++;
cbuf[1] = *ncp++;
cbuf[2] = *ncp;
ncp = &"JanFebMarAprMayJunJulAugSepOctNovDec"[t->mon*3];
cbuf[4] = *ncp++;
cbuf[5] = *ncp++;
cbuf[6] = *ncp;
ct_numb(cbuf+8, t->mday);
ct_numb(cbuf+11, t->hour+100);
ct_numb(cbuf+14, t->min+100);
ct_numb(cbuf+17, t->sec+100);
ncp = t->zone;
cbuf[20] = *ncp++;
cbuf[21] = *ncp++;
cbuf[22] = *ncp;
ct_numb(cbuf+24, (t->year+1900) / 100 + 100);
ct_numb(cbuf+26, t->year+100);
return cbuf;
}
static
dysize(int y)
{
if(y%4 == 0 && (y%100 != 0 || y%400 == 0))
return 366;
return 365;
}
static
void
ct_numb(char *cp, int n)
{
cp[0] = ' ';
if(n >= 10)
cp[0] = (n/10)%10 + '0';
cp[1] = n%10 + '0';
}
static
void
readtimezone(void)
{
char buf[TZSIZE*11+30], *p;
int i;
memset(buf, 0, sizeof(buf));
i = open("/env/timezone", 0);
if(i < 0)
goto error;
if(read(i, buf, sizeof(buf)) >= sizeof(buf)){
close(i);
goto error;
}
close(i);
p = buf;
if(rd_name(&p, timezone.stname))
goto error;
if(rd_long(&p, &timezone.stdiff))
goto error;
if(rd_name(&p, timezone.dlname))
goto error;
if(rd_long(&p, &timezone.dldiff))
goto error;
for(i=0; i<TZSIZE; i++) {
if(rd_long(&p, &timezone.dlpairs[i]))
goto error;
if(timezone.dlpairs[i] == 0)
return;
}
error:
timezone.stdiff = 0;
strcpy(timezone.stname, "GMT");
timezone.dlpairs[0] = 0;
}
static
rd_name(char **f, char *p)
{
int c, i;
for(;;) {
c = *(*f)++;
if(c != ' ' && c != '\n')
break;
}
for(i=0; i<3; i++) {
if(c == ' ' || c == '\n')
return 1;
*p++ = c;
c = *(*f)++;
}
if(c != ' ' && c != '\n')
return 1;
*p = 0;
return 0;
}
static
rd_long(char **f, long *p)
{
int c, s;
long l;
s = 0;
for(;;) {
c = *(*f)++;
if(c == '-') {
s++;
continue;
}
if(c != ' ' && c != '\n')
break;
}
if(c == 0) {
*p = 0;
return 0;
}
l = 0;
for(;;) {
if(c == ' ' || c == '\n')
break;
if(c < '0' || c > '9')
return 1;
l = l*10 + c-'0';
c = *(*f)++;
}
if(s)
l = -l;
*p = l;
return 0;
/*
* We have no way to report errors,
* so we just ignore them here.
*/
tz = tzload("local");
tmtime(&tm, abs, tz);
return asctime(&tm);
}

View file

@ -1,202 +1,11 @@
#include <u.h>
#include <libc.h>
#define TZSIZE 150
static void readtimezone(void);
static int rd_name(char**, char*);
static int rd_long(char**, long*);
static
struct
{
char stname[4];
char dlname[4];
long stdiff;
long dldiff;
long dlpairs[TZSIZE];
} timezone;
#define SEC2MIN 60L
#define SEC2HOUR (60L*SEC2MIN)
#define SEC2DAY (24L*SEC2HOUR)
/*
* days per month plus days/year
*/
static int dmsize[] =
{
365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static int ldmsize[] =
{
366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
/*
* return the days/month for the given year
*/
static int *
yrsize(int y)
{
if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
return ldmsize;
else
return dmsize;
}
/*
* compute seconds since Jan 1 1970 GMT
* and convert to our timezone.
*/
long
tm2sec(Tm *tm)
{
long secs, *p;
int i, yday, year, *d2m;
Tm tt;
if(strcmp(tm->zone, "GMT") != 0 && timezone.stname[0] == 0)
readtimezone();
secs = 0;
/*
* seconds per year
*/
year = tm->year + 1900;
for(i = 1970; i < year; i++){
d2m = yrsize(i);
secs += d2m[0] * SEC2DAY;
}
/*
* if mday is set, use mon and mday to compute yday
*/
if(tm->mday){
yday = 0;
d2m = yrsize(year);
for(i=0; i<tm->mon; i++)
yday += d2m[i+1];
yday += tm->mday-1;
}else{
yday = tm->yday;
}
secs += yday * SEC2DAY;
/*
* hours, minutes, seconds
*/
secs += tm->hour * SEC2HOUR;
secs += tm->min * SEC2MIN;
secs += tm->sec;
/*
* Only handles zones mentioned in /env/timezone,
* but things get too ambiguous otherwise.
*/
if(strcmp(tm->zone, timezone.stname) == 0)
secs -= timezone.stdiff;
else if(strcmp(tm->zone, timezone.dlname) == 0)
secs -= timezone.dldiff;
else if(tm->zone[0] == 0){
secs -= timezone.dldiff;
for(p = timezone.dlpairs; *p; p += 2)
if(secs >= p[0] && secs < p[1])
break;
if(*p == 0){
secs += timezone.dldiff;
secs -= timezone.stdiff;
}
}
return secs;
}
static
void
readtimezone(void)
{
char buf[TZSIZE*11+30], *p;
int i;
memset(buf, 0, sizeof(buf));
i = open("/env/timezone", 0);
if(i < 0)
goto error;
if(read(i, buf, sizeof(buf)) >= sizeof(buf))
goto error;
close(i);
p = buf;
if(rd_name(&p, timezone.stname))
goto error;
if(rd_long(&p, &timezone.stdiff))
goto error;
if(rd_name(&p, timezone.dlname))
goto error;
if(rd_long(&p, &timezone.dldiff))
goto error;
for(i=0; i<TZSIZE; i++) {
if(rd_long(&p, &timezone.dlpairs[i]))
goto error;
if(timezone.dlpairs[i] == 0)
return;
}
error:
timezone.stdiff = 0;
strcpy(timezone.stname, "GMT");
timezone.dlpairs[0] = 0;
}
static int
rd_name(char **f, char *p)
{
int c, i;
for(;;) {
c = *(*f)++;
if(c != ' ' && c != '\n')
break;
}
for(i=0; i<3; i++) {
if(c == ' ' || c == '\n')
return 1;
*p++ = c;
c = *(*f)++;
}
if(c != ' ' && c != '\n')
return 1;
*p = 0;
return 0;
}
static int
rd_long(char **f, long *p)
{
int c, s;
long l;
s = 0;
for(;;) {
c = *(*f)++;
if(c == '-') {
s++;
continue;
}
if(c != ' ' && c != '\n')
break;
}
if(c == 0) {
*p = 0;
return 0;
}
l = 0;
for(;;) {
if(c == ' ' || c == '\n')
break;
if(c < '0' || c > '9')
return 1;
l = l*10 + c-'0';
c = *(*f)++;
}
if(s)
l = -l;
*p = l;
return 0;
tt = *tm;
return tmnorm(&tt);
}

996
sys/src/libc/port/date.c Normal file
View file

@ -0,0 +1,996 @@
#include <u.h>
#include <libc.h>
typedef struct Tzabbrev Tzabbrev;
typedef struct Tzoffpair Tzoffpair;
#define Ctimefmt "WW MMM _D hh:mm:ss ZZZ YYYY"
#define P(pad, w) ((pad) < (w) ? 0 : pad - w)
enum {
Tzsize = 150,
Nsec = 1000*1000*1000,
Usec = 1000*1000,
Msec = 1000,
Daysec = (vlong)24*3600,
Days400y = 365*400 + 4*25 - 3,
Days4y = 365*4 + 1,
};
enum {
Cend,
Cspace,
Cnum,
Cletter,
Cpunct,
};
struct Tzone {
char tzname[32];
char stname[16];
char dlname[16];
long stdiff;
long dldiff;
long dlpairs[150];
};
static QLock zlock;
static int nzones;
static Tzone **zones;
static int mdays[] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
static char *wday[] = {
"Sunday","Monday","Tuesday",
"Wednesday","Thursday","Friday",
"Saturday", nil,
};
static char *month[] = {
"January", "February", "March",
"April", "May", "June", "July",
"August", "September", "October",
"November", "December", nil
};
struct Tzabbrev {
char *abbr;
char *name;
};
struct Tzoffpair {
char *abbr;
int off;
};
#define isalpha(c)\
(((c)|0x60) >= 'a' && ((c)|0x60) <= 'z')
/* Obsolete time zone names. Hardcoded to match RFC5322 */
static Tzabbrev tzabbrev[] = {
{"UT", "GMT"}, {"GMT", "GMT"}, {"UTC", "GMT"},
{"EST", "US_Eastern"}, {"EDT", "US_Eastern"},
{"CST", "US_Central"}, {"CDT", "US_Central"},
{"MST", "US_Mountain"}, {"MDT", "US_Mountain"},
{"PST", "US_Pacific"}, {"PDT", "US_Pacific"},
{nil},
};
/* Military timezone names */
static Tzoffpair milabbrev[] = {
{"A", -1*3600}, {"B", -2*3600}, {"C", -3*3600},
{"D", -4*3600}, {"E", -5*3600}, {"F", -6*3600},
{"G", -7*3600}, {"H", -8*3600}, {"I", -9*3600},
{"K", -10*3600}, {"L", -11*3600}, {"M", -12*3600},
{"N", +1*3600}, {"O", +2*3600}, {"P", +3*3600},
{"Q", +4*3600}, {"R", +5*3600}, {"S", +6*3600},
{"T", +7*3600}, {"U", +8*3600}, {"V", +9*3600},
{"W", +10*3600}, {"X", +11*3600}, {"Y", +12*3600},
{"Z", 0}, {nil, 0}
};
static vlong
mod(vlong a, vlong b)
{
vlong r;
r = a % b;
if(r < 0)
r += b;
return r;
}
static int
isleap(int y)
{
return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
}
static int
rdname(char **f, char *p, int n)
{
char *s, *e;
for(s = *f; *s; s++)
if(*s != ' ' && *s != '\t' && *s != '\n')
break;
e = s + n;
for(; *s && s != e; s++) {
if(*s == ' ' || *s == '\t' || *s == '\n')
break;
*p++ = *s;
}
*p = 0;
if(n - (e - s) < 3 || *s != ' ' && *s != '\t' && *s != '\n'){
werrstr("truncated name");
return -1;
}
*f = s;
return 0;
}
static int
rdlong(char **f, long *p)
{
int c, s;
long l;
s = 0;
while((c = *(*f)++) != 0){
if(c == '-')
s++;
else if(c != ' ' && c != '\n')
break;
}
if(c == 0) {
*p = 0;
return 0;
}
l = 0;
for(;;) {
if(c == ' ' || c == '\n')
break;
if(c < '0' || c > '9'){
werrstr("non-number %c in name", c);
return -1;
}
l = l*10 + c-'0';
c = *(*f)++;
}
if(s)
l = -l;
*p = l;
return 0;
}
static int
loadzone(Tzone *tz, char *name)
{
char buf[Tzsize*11+30], path[128], *p;
int i, f, r;
memset(tz, 0, sizeof(Tzone));
if(strcmp(name, "local") == 0)
snprint(path, sizeof(path), "/env/timezone");
else
snprint(path, sizeof(path), "/adm/timezone/%s", name);
memset(buf, 0, sizeof(buf));
if((f = open(path, 0)) == -1)
return -1;
r = read(f, buf, sizeof(buf));
close(f);
if(r == sizeof(buf) || r == -1)
return -1;
buf[r] = 0;
p = buf;
if(rdname(&p, tz->stname, sizeof(tz->stname)) == -1)
return -1;
if(rdlong(&p, &tz->stdiff) == -1)
return -1;
if(rdname(&p, tz->dlname, sizeof(tz->dlname)) == -1)
return -1;
if(rdlong(&p, &tz->dldiff) == -1)
return -1;
for(i=0; i < Tzsize; i++) {
if(rdlong(&p, &tz->dlpairs[i]) == -1){
werrstr("invalid transition time");
return -1;
}
if(tz->dlpairs[i] == 0)
return 0;
}
werrstr("invalid timezone %s", name);
return -1;
}
Tzone*
tzload(char *tzname)
{
Tzone *tz, **newzones;
int i;
if(tzname == nil)
tzname = "GMT";
qlock(&zlock);
for(i = 0; i < nzones; i++){
tz = zones[i];
if(strcmp(tz->stname, tzname) == 0)
goto found;
if(strcmp(tz->dlname, tzname) == 0)
goto found;
if(strcmp(tz->tzname, tzname) == 0)
goto found;
}
tz = malloc(sizeof(Tzone));
if(tz == nil)
goto error;
newzones = realloc(zones, (nzones + 1) * sizeof(Tzone*));
if(newzones == nil)
goto error;
if(loadzone(tz, tzname) != 0)
goto error;
if(snprint(tz->tzname, sizeof(tz->tzname), tzname) >= sizeof(tz->tzname)){
werrstr("timezone name too long");
return nil;
}
zones = newzones;
zones[nzones] = tz;
nzones++;
found:
qunlock(&zlock);
return tz;
error:
free(tz);
qunlock(&zlock);
return nil;
}
static void
tzoffset(Tzone *tz, vlong abs, Tm *tm)
{
long dl, *p;
dl = 0;
if(tz == nil){
snprint(tm->zone, sizeof(tm->zone), "GMT");
tm->tzoff = 0;
return;
}
for(p = tz->dlpairs; *p; p += 2)
if(abs > p[0] && abs <= p[1]){
dl = 1;
break;
}
if(dl){
snprint(tm->zone, sizeof(tm->zone), tz->dlname);
tm->tzoff = tz->dldiff;
}else{
snprint(tm->zone, sizeof(tm->zone), tz->stname);
tm->tzoff = tz->stdiff;
}
}
static Tm*
tmfill(Tm *tm, vlong abs, vlong nsec)
{
vlong zrel, j, y, m, d, t, e;
int i;
zrel = abs + tm->tzoff;
t = zrel % Daysec;
e = zrel / Daysec;
if(t < 0){
t += Daysec;
e -= 1;
}
t += nsec/Nsec;
tm->sec = mod(t, 60);
t /= 60;
tm->min = mod(t, 60);
t /= 60;
tm->hour = mod(t, 24);
tm->wday = mod((e + 4), 7);
/*
* Split up year, month, day.
*
* Implemented according to "Algorithm 199,
* conversions between calendar date and
* Julian day number", Robert G. Tantzen,
* Air Force Missile Development
* Center, Holloman AFB, New Mex.
*
* Lots of magic.
*/
j = (zrel + 2440588 * Daysec) / (Daysec) - 1721119;
y = (4 * j - 1) / Days400y;
j = 4 * j - 1 - Days400y * y;
d = j / 4;
j = (4 * d + 3) / Days4y;
d = 4 * d + 3 - Days4y * j;
d = (d + 4) / 4 ;
m = (5 * d - 3) / 153;
d = 5 * d - 3 - 153 * m;
d = (d + 5) / 5;
y = 100 * y + j;
if(m < 10)
m += 3;
else{
m -= 9;
y++;
}
/* there's no year 0 */
if(y <= 0)
y--;
/* and if j negative, the day and month are also negative */
if(m < 0)
m += 12;
if(d < 0)
d += mdays[m - 1];
tm->yday = d;
for(i = 0; i < m - 1; i++)
tm->yday += mdays[i];
if(m > 1 && isleap(y))
tm->yday++;
tm->year = y - 1900;
tm->mon = m - 1;
tm->mday = d;
tm->nsec = mod(nsec, Nsec);
return tm;
}
Tm*
tmtime(Tm *tm, vlong abs, Tzone *tz)
{
return tmtimens(tm, abs, 0, tz);
}
Tm*
tmtimens(Tm *tm, vlong abs, int ns, Tzone *tz)
{
tm->tz = tz;
tzoffset(tz, abs, tm);
return tmfill(tm, abs, ns);
}
Tm*
tmnow(Tm *tm, Tzone *tz)
{
vlong ns;
ns = nsec();
return tmtimens(tm, nsec()/Nsec, mod(ns, Nsec), tz);
}
vlong
tmnorm(Tm *tm)
{
vlong c, yadj, j, abs, y, m, d;
if(tm->mon > 1){
m = tm->mon - 2;
y = tm->year + 1900;
}else{
m = tm->mon + 10;
y = tm->year + 1899;
}
d = tm->mday;
c = y / 100;
yadj = y - 100 * c;
j = (c * Days400y / 4 +
Days4y * yadj / 4 +
(153 * m + 2)/5 + d -
719469);
abs = j * Daysec;
abs += tm->hour * 3600;
abs += tm->min * 60;
abs += tm->sec;
if(tm->tz){
tzoffset(tm->tz, abs - tm->tzoff, tm);
tzoffset(tm->tz, abs - tm->tzoff, tm);
}
abs -= tm->tzoff;
tmfill(tm, abs, tm->nsec);
return abs;
}
static int
τconv(Fmt *f)
{
int depth, n, v, w, h, m, c0, sgn, pad, off;
char *p, *am;
Tmfmt tf;
Tm *tm;
n = 0;
tf = va_arg(f->args, Tmfmt);
tm = tf.tm;
p = tf.fmt;
if(p == nil)
p = Ctimefmt;
while(*p){
w = 1;
pad = 0;
while(*p == '_'){
pad++;
p++;
}
c0 = *p++;
while(c0 && *p == c0){
w++;
p++;
}
pad += w;
switch(c0){
case 0:
break;
case 'Y':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->year + 1900); break;
case 2: n += fmtprint(f, "%*d", pad, tm->year % 100); break;
case 4: n += fmtprint(f, "%*d", pad, tm->year + 1900); break;
default: goto badfmt;
}
break;
case 'M':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->mon + 1); break;
case 2: n += fmtprint(f, "%*s%02d", pad-2, "", tm->mon + 1); break;
case 3: n += fmtprint(f, "%*.3s", pad, month[tm->mon]); break;
case 4: n += fmtprint(f, "%*s", pad, month[tm->mon]); break;
default: goto badfmt;
}
break;
case 'D':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->mday); break;
case 2: n += fmtprint(f, "%*s%02d", pad-2, "", tm->mday); break;
default: goto badfmt;
}
break;
case 'W':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->wday + 1); break;
case 2: n += fmtprint(f, "%*.3s", pad, wday[tm->wday]); break;
case 3: n += fmtprint(f, "%*s", pad, wday[tm->wday]); break;
default: goto badfmt;
}
break;
case 'H':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->hour % 12); break;
case 2: n += fmtprint(f, "%*s%02d", pad-2, "", tm->hour % 12); break;
default: goto badfmt;
}
break;
case 'h':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->hour); break;
case 2: n += fmtprint(f, "%*s%02d", pad-2, "", tm->hour); break;
default: goto badfmt;
}
break;
case 'm':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->min); break;
case 2: n += fmtprint(f, "%*s%02d", pad-2, "", tm->min); break;
default: goto badfmt;
}
break;
case 's':
switch(w){
case 1: n += fmtprint(f, "%*d", pad, tm->sec); break;
case 2: n += fmtprint(f, "%*s%02d", pad-2, "", tm->sec); break;
default: goto badfmt;
}
break;
case 't':
v = tm->nsec / (1000*1000);
switch(w){
case 1: n += fmtprint(f, "%*d", pad, v % 1000); break;
case 2:
case 3: n += fmtprint(f, "%*s%03d", P(pad, 3), "", v % 1000); break;
default: goto badfmt;
}
break;
case 'u':
v = tm->nsec / 1000;
switch(w){
case 1: n += fmtprint(f, "%*d", pad, v % 1000); break;
case 2: n += fmtprint(f, "%*s%03d", P(pad, 3), "", v % 1000); break;
case 3: n += fmtprint(f, "%*d", P(pad, 6), v); break;
case 4: n += fmtprint(f, "%*s%06d", P(pad, 6), "", v); break;
default: goto badfmt;
}
break;
case 'n':
v = tm->nsec;
switch(w){
case 1: n += fmtprint(f, "%*d", pad, v%1000); break;
case 2: n += fmtprint(f, "%*s%03d", P(pad, 3), "", v % 1000); break;
case 3: n += fmtprint(f, "%*d", pad , v%(1000*1000)); break;
case 4: n += fmtprint(f, "%*s%06d", P(pad, 6), "", v%(1000000)); break;
case 5: n += fmtprint(f, "%*d", pad, v); break;
case 6: n += fmtprint(f, "%*s%09d", P(pad, 9), "", v); break;
default: goto badfmt;
}
break;
case 'z':
if(w != 1)
goto badfmt;
case 'Z':
sgn = (tm->tzoff < 0) ? '-' : '+';
off = (tm->tzoff < 0) ? -tm->tzoff : tm->tzoff;
h = off/3600;
m = (off/60)%60;
if(w < 3 && pad < 5)
pad = 5;
switch(w){
case 1: n += fmtprint(f, "%*s%c%02d%02d", pad-5, "", sgn, h, m); break;
case 2: n += fmtprint(f, "%*s%c%02d:%02d", pad-5, "", sgn, h, m); break;
case 3: n += fmtprint(f, "%*s", pad, tm->zone); break;
}
break;
case 'A':
case 'a':
if(w != 1)
goto badfmt;
if(c0 == 'a')
am = (tm->hour < 12) ? "am" : "pm";
else
am = (tm->hour < 12) ? "AM" : "PM";
n += fmtprint(f, "%*s", pad, am);
break;
case '[':
depth = 1;
while(*p){
if(*p == '[')
depth++;
if(*p == ']')
depth--;
if(*p == '\\')
p++;
if(depth == 0)
break;
fmtrune(f, *p++);
}
if(*p++ != ']')
goto badfmt;
break;
default:
while(w-- > 0)
n += fmtrune(f, c0);
break;
}
}
return n;
badfmt:
werrstr("garbled format %s", tf.fmt);
return -1;
}
static int
getnum(char **ps, int maxw, int *ok)
{
char *s, *e;
int n;
n = 0;
e = *ps + maxw;
for(s = *ps; s != e && *s >= '0' && *s <= '9'; s++){
n *= 10;
n += *s - '0';
}
*ok = s != *ps;
*ps = s;
return n;
}
static int
lookup(char **s, char **tab, int len, int *ok)
{
int nc, i;
*ok = 0;
for(i = 0; *tab; tab++){
nc = (len != -1) ? len : strlen(*tab);
if(cistrncmp(*s, *tab, nc) == 0){
*s += nc;
*ok = 1;
return i;
}
i++;
}
*ok = 0;
return -1;
}
Tm*
tmparse(Tm *tm, char *fmt, char *str, Tzone *tz, char **ep)
{
int depth, n, w, c0, zs, z0, z1, md, ampm, zoned, sloppy, tzo, ok;
vlong abs;
char *s, *p, *q;
Tzone *zparsed;
Tzabbrev *a;
Tzoffpair *m;
p = fmt;
s = str;
tzo = 0;
ampm = -1;
zoned = 0;
zparsed = nil;
sloppy = 0;
/* Default all fields */
tmtime(tm, 0, nil);
if(*p == '~'){
sloppy = 1;
p++;
}
while(*p){
w = 1;
c0 = *p++;
if(c0 == '?'){
w = -1;
c0 = *p++;
}
while(*p == c0){
if(w != -1) w++;
p++;
}
ok = 1;
switch(c0){
case 'Y':
switch(w){
case -1:
tm->year = getnum(&s, 4, &ok);
if(tm->year > 100) tm->year -= 1900;
break;
case 1: tm->year = getnum(&s, 4, &ok) - 1900; break;
case 2: tm->year = getnum(&s, 2, &ok); break;
case 3:
case 4: tm->year = getnum(&s, 4, &ok) - 1900; break;
default: goto badfmt;
}
break;
case 'M':
switch(w){
case -1:
tm->mon = getnum(&s, 2, &ok) - 1;
if(!ok) tm->mon = lookup(&s, month, -1, &ok);
if(!ok) tm->mon = lookup(&s, month, 3, &ok);
break;
case 1:
case 2: tm->mon = getnum(&s, 2, &ok) - 1; break;
case 3: tm->mon = lookup(&s, month, 3, &ok); break;
case 4: tm->mon = lookup(&s, month, -1, &ok); break;
default: goto badfmt;
}
break;
case 'D':
switch(w){
case -1:
case 1:
case 2: tm->mday = getnum(&s, 2, &ok); break;
default: goto badfmt;
}
break;
case 'W':
switch(w){
case -1:
tm->wday = lookup(&s, wday, -1, &ok);
if(!ok) tm->wday = lookup(&s, wday, 3, &ok);
if(!ok) tm->wday = getnum(&s, 1, &ok) - 1;
break;
case 1: tm->wday = getnum(&s, 1, &ok) - 1; break;
case 2: tm->wday = lookup(&s, wday, 3, &ok); break;
case 3: tm->wday = lookup(&s, wday, -1, &ok); break;
default: goto badfmt;
}
break;
case 'h':
switch(w){
case -1:
case 1:
case 2: tm->hour = getnum(&s, 2, &ok); break;
default: goto badfmt;
}
break;
case 'm':
switch(w){
case -1:
case 1:
case 2: tm->min = getnum(&s, 2, &ok); break;
default: goto badfmt;
}
break;
case 's':
switch(w){
case -1:
case 1:
case 2: tm->sec = getnum(&s, 2, &ok); break;
default: goto badfmt;
}
break;
case 't':
switch(w){
case -1:
case 1:
case 2:
case 3: tm->nsec += getnum(&s, 3, &ok)*1000000; break;
}
break;
case 'u':
switch(w){
case -1:
case 1:
case 2: tm->nsec += getnum(&s, 3, &ok)*1000; break;
case 3:
case 4: tm->nsec += getnum(&s, 6, &ok)*1000; break;
}
break;
case 'n':
switch(w){
case 1:
case 2: tm->nsec += getnum(&s, 3, &ok); break;
case 3:
case 4: tm->nsec += getnum(&s, 6, &ok); break;
case -1:
case 5:
case 6: tm->nsec += getnum(&s, 9, &ok); break;
}
break;
case 'z':
if(w != 1)
goto badfmt;
case 'Z':
zs = 0;
zoned = 1;
switch(*s++){
case '+': zs = 1; break;
case '-': zs = -1; break;
default: s--; break;
}
q = s;
switch(w){
case -1:
case 3:
for(a = tzabbrev; a->abbr; a++){
n = strlen(a->abbr);
if(cistrncmp(s, a->abbr, n) == 0 && !isalpha(s[n]))
break;
}
if(a->abbr != nil){
s += strlen(a->abbr);
zparsed = tzload(a->name);
if(zparsed == nil){
werrstr("unloadable zone %s (%s)", a->abbr, a->name);
if(w != -1)
return nil;
}
goto Zoneparsed;
}
for(m = milabbrev; m->abbr != nil; m++){
n = strlen(m->abbr);
if(cistrncmp(s, m->abbr, n) == 0 && !isalpha(s[n]))
break;
}
if(m->abbr != nil){
snprint(tm->zone, sizeof(tm->zone), "%s", m->abbr);
tzo = m->off;
goto Zoneparsed;
}
if(w != -1)
break;
/* fall through */
case 1:
/* offset: [+-]hhmm */
z0 = getnum(&s, 4, &ok);
if(s - q == 4){
z1 = z0 % 100;
if(z0/100 > 13 || z1 >= 60)
goto baddate;
tzo = zs*(3600*(z0/100) + 60*z1);
snprint(tm->zone, sizeof(tm->zone), "%c%02d%02d", zs<0?'-':'+', z0/100, z1);
goto Zoneparsed;
}
if(w != -1)
goto baddate;
/* fall through */
case 2:
s = q;
/* offset: [+-]hh:mm */
z0 = getnum(&s, 2, &ok);
if(*s++ != ':')
break;
z1 = getnum(&s, 2, &ok);
if(z1 > 60)
break;
tzo = zs*(3600*z0 + 60*z1);
snprint(tm->zone, sizeof(tm->zone), "%c%d02:%02d", zs<0?'-':'+', z0, z1);
goto Zoneparsed;
}
if(w != -1)
goto baddate;
/*
* Final fuzzy fallback: If we have what looks like an
* unknown timezone abbreviation, keep the zone name,
* but give it a timezone offset of 0. This allows us
* to avoid rejecting zones outside of RFC5322.
*/
for(s = q; *s; s++)
if(!isalpha(*s))
break;
if(s - q >= 3 && !isalpha(*s)){
strncpy(tm->zone, q, s - q);
tzo = 0;
ok = 1;
goto Zoneparsed;
}
goto baddate;
Zoneparsed:
break;
case 'A':
case 'a':
if(cistrncmp(s, "am", 2) == 0)
ampm = 0;
else if(cistrncmp(s, "pm", 2) == 0)
ampm = 1;
else
goto baddate;
s += 2;
break;
case '[':
depth = 1;
while(*p){
if(*p == '[')
depth++;
if(*p == ']')
depth--;
if(*p == '\\')
p++;
if(depth == 0)
break;
if(*s == 0)
goto baddate;
if(*s++ != *p++)
goto baddate;
}
if(*p != ']')
goto badfmt;
p++;
break;
case '_':
case ',':
case ' ':
if(*s != ' ' && *s != '\t' && *s != ',' && *s != '\n' && *s != '\0')
goto baddate;
p += strspn(p, " ,_\t\n");
s += strspn(s, " ,\t\n");
break;
default:
if(*s == 0)
goto baddate;
if(*s++ != c0)
goto baddate;
break;
}
if(!ok)
goto baddate;
}
if(*p != '\0')
goto baddate;
if(ep != nil)
*ep = s;
if(!sloppy && ampm != -1 && (tm->hour < 1 || tm->hour > 12))
goto baddate;
if(ampm == 0 && tm->hour == 12)
tm->hour = 0;
else if(ampm == 1 && tm->hour < 12)
tm->hour += 12;
/*
* If we're allowing sloppy date ranges,
* we'll normalize out of range values.
*/
if(!sloppy){
if(tm->yday < 0 || tm->yday > 365 + isleap(tm->year + 1900))
goto baddate;
if(tm->wday < 0 || tm->wday > 6)
goto baddate;
if(tm->mon < 0 || tm->mon > 11)
goto baddate;
md = mdays[tm->mon];
if(tm->mon == 1 && isleap(tm->year + 1900))
md++;
if(tm->mday < 0 || tm->mday > md)
goto baddate;
if(tm->hour < 0 || tm->hour > 24)
goto baddate;
if(tm->min < 0 || tm->min > 59)
goto baddate;
if(tm->sec < 0 || tm->sec > 60)
goto baddate;
if(tm->nsec < 0 || tm->nsec > Nsec)
goto baddate;
if(tm->wday < 0 || tm->wday > 6)
goto baddate;
}
/*
* Normalizing gives us the local time,
* but because we havnen't applied the
* timezone, we think we're GMT. So, we
* need to shift backwards. Then, we move
* the "GMT that was local" back to local
* time.
*/
abs = tmnorm(tm);
tm->tzoff = tzo;
if(!zoned)
tzoffset(tz, abs, tm);
else if(zparsed != nil){
tzoffset(zparsed, abs, tm);
tzoffset(zparsed, abs + tm->tzoff, tm);
}
abs -= tm->tzoff;
if(tz != nil || !zoned)
tmtimens(tm, abs, tm->nsec, tz);
return tm;
baddate:
werrstr("invalid date %s", str);
return nil;
badfmt:
werrstr("garbled format %s near '%s'", fmt, p);
return nil;
}
Tmfmt
tmfmt(Tm *d, char *fmt)
{
return (Tmfmt){fmt, d};
}
void
tmfmtinstall(void)
{
fmtinstall(L'τ', τconv);
}
/* These legacy functions need access to τconv */
static char*
dotmfmt(Fmt *f, ...)
{
static char buf[30];
va_list ap;
va_start(ap, f);
f->runes = 0;
f->start = buf;
f->to = buf;
f->stop = buf + sizeof(buf) - 1;
f->flush = nil;
f->farg = nil;
f->nfmt = 0;
f->args = ap;
τconv(f);
va_end(ap);
buf[sizeof(buf) - 1] = 0;
return buf;
}
char*
asctime(Tm* tm)
{
Tmfmt tf;
Fmt f;
tf = tmfmt(tm, "WW MMM _D hh:mm:ss ZZZ YYYY\n");
return dotmfmt(&f, tf);
}

View file

@ -20,6 +20,7 @@ CFILES=\
cleanname.c\
crypt.c\
ctype.c\
date.c\
encodefmt.c\
execl.c\
exits.c\