1041 lines
20 KiB
C
1041 lines
20 KiB
C
#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));
|
|
f = open(path, OREAD|OCEXEC);
|
|
if(f < 0)
|
|
return -1;
|
|
r = read(f, buf, sizeof(buf));
|
|
close(f);
|
|
if(r < 0 || r >= sizeof(buf))
|
|
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 - 1;
|
|
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;
|
|
/* Ignore '?' so we can share parse and format strings */
|
|
case '?':
|
|
continue;
|
|
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, *local;
|
|
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++;
|
|
}
|
|
|
|
/* Skip whitespace */
|
|
for(;; s++) {
|
|
switch(*s) {
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case '\f':
|
|
case '\r':
|
|
case '\v':
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
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:
|
|
/*
|
|
* Ugly Hack:
|
|
* Ctime is defined as printing a 3-character timezone
|
|
* name. The timezone name is ambiguous. For example,
|
|
* EST refers to both Australian and American eastern
|
|
* time. On top of that, we don't want to make the
|
|
* tzabbrev table exhaustive. So, we put in this hack:
|
|
*
|
|
* Before we consult the well known table of timezones,
|
|
* we check if the local time matches the timezone name.
|
|
*
|
|
* If you want unambiguous timezone parsing, use numeric
|
|
* timezone offsets (Z, ZZ formats).
|
|
*/
|
|
if((local = tzload("local")) != nil){
|
|
if(cistrncmp(s, local->stname, strlen(local->stname)) == 0){
|
|
s += strlen(local->stname);
|
|
zparsed = local;
|
|
goto Zoneparsed;
|
|
}
|
|
if(cistrncmp(s, local->dlname, strlen(local->dlname)) == 0){
|
|
s += strlen(local->dlname);
|
|
zparsed = local;
|
|
goto Zoneparsed;
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
|