libc, seconds: new time and date apis (try 2)
Redo date handling in libc almost entirely. This allows handling dates and times from outside your timezones, fixes timezone loading in multithreaded applications, and allows parsing and formatting using custom format strings. As a test of the APIs, we replace the formatting code in seconds(1), shrinking it massively. The last commit missed a few removals, and made it unnecessarily hard to do an update.
This commit is contained in:
parent
f380851ddb
commit
8b3efcfc4e
7 changed files with 1207 additions and 900 deletions
|
@ -33,269 +33,34 @@
|
|||
#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;
|
||||
|
||||
/* No error checking: the API doesn't allow it. */
|
||||
tz = tmgetzone("local");
|
||||
tmtime(&tm, tim, tz);
|
||||
return &tm;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
/* No error checking: the API doesn't allow it. */
|
||||
tz = tmgetzone("local");
|
||||
tmtime(&tm, abs, tz);
|
||||
return asctime(&tm);
|
||||
}
|
||||
|
|
|
@ -1,202 +1,9 @@
|
|||
#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;
|
||||
|
||||
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;
|
||||
tmnorm(tm);
|
||||
return tm->abs;
|
||||
}
|
||||
|
|
859
sys/src/libc/port/date.c
Normal file
859
sys/src/libc/port/date.c
Normal file
|
@ -0,0 +1,859 @@
|
|||
#include <u.h>
|
||||
#include <libc.h>
|
||||
|
||||
typedef struct Tzabbrev Tzabbrev;
|
||||
typedef struct Tzoffpair Tzoffpair;
|
||||
|
||||
#define Ctimefmt "W MMM _D hh:mm:ss ZZZ YYYY\n"
|
||||
enum {
|
||||
Tzsize = 150,
|
||||
Nsec = 1000*1000*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[16];
|
||||
char stname[4];
|
||||
char dlname[4];
|
||||
long stdiff;
|
||||
long dldiff;
|
||||
long dlpairs[Tzsize];
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/* 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 int
|
||||
isleap(int y)
|
||||
{
|
||||
return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);
|
||||
}
|
||||
|
||||
static int
|
||||
rdname(char **f, char *p)
|
||||
{
|
||||
int c, i;
|
||||
|
||||
while((c = *(*f)++) != 0)
|
||||
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
|
||||
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')
|
||||
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;
|
||||
|
||||
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;
|
||||
p = buf;
|
||||
if(rdname(&p, tz->stname))
|
||||
return -1;
|
||||
if(rdlong(&p, &tz->stdiff))
|
||||
return -1;
|
||||
if(rdname(&p, tz->dlname))
|
||||
return -1;
|
||||
if(rdlong(&p, &tz->dldiff))
|
||||
return -1;
|
||||
for(i=0; i < Tzsize; i++) {
|
||||
if(rdlong(&p, &tz->dlpairs[i]))
|
||||
return -1;
|
||||
if(tz->dlpairs[i] == 0)
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Tzone*
|
||||
tmgetzone(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
|
||||
getzoneoff(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;
|
||||
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;
|
||||
|
||||
tm->abs = abs;
|
||||
zrel = abs + tm->tzoff;
|
||||
t = zrel % Daysec;
|
||||
e = zrel / Daysec;
|
||||
if(t < 0){
|
||||
t += Daysec;
|
||||
e -= 1;
|
||||
}
|
||||
|
||||
t += nsec/Nsec;
|
||||
tm->sec = t % 60;
|
||||
t /= 60;
|
||||
tm->min = t % 60;
|
||||
t /= 60;
|
||||
tm->hour = t;
|
||||
tm->wday = (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 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
|
||||
tm->yday++;
|
||||
tm->year = y - 1900;
|
||||
tm->mon = m - 1;
|
||||
tm->mday = d;
|
||||
tm->nsec = 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;
|
||||
getzoneoff(tz, abs, tm);
|
||||
return tmfill(tm, abs, ns);
|
||||
}
|
||||
|
||||
Tm*
|
||||
tmnow(Tm *tm, Tzone *tz)
|
||||
{
|
||||
vlong ns;
|
||||
|
||||
ns = nsec();
|
||||
return tmtimens(tm, nsec()/Nsec, ns%Nsec, tz);
|
||||
}
|
||||
|
||||
Tm*
|
||||
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 - 1901;
|
||||
}
|
||||
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;
|
||||
abs -= tm->tzoff;
|
||||
return tmfill(tm, abs, tm->nsec);
|
||||
}
|
||||
|
||||
static int
|
||||
τconv(Fmt *f)
|
||||
{
|
||||
int depth, n, 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, "%*.3s", pad, wday[tm->wday]); break;
|
||||
case 2: 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 '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:
|
||||
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)
|
||||
{
|
||||
int depth, w, c0, zs, z0, z1, ampm, zoned, sloppy, tzo, ok;
|
||||
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);
|
||||
break;
|
||||
case 1: tm->wday = lookup(&s, wday, 3, &ok); break;
|
||||
case 2: 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 '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;
|
||||
}
|
||||
switch(w){
|
||||
case -1:
|
||||
case 3:
|
||||
for(a = tzabbrev; a->abbr; a++)
|
||||
if(strncmp(s, a->abbr, strlen(a->abbr)) == 0)
|
||||
break;
|
||||
if(a->abbr != nil){
|
||||
s += strlen(a->abbr);
|
||||
zparsed = tmgetzone(a->name);
|
||||
if(zparsed == nil){
|
||||
werrstr("unloadable zone %s (%s)", a->abbr, a->name);
|
||||
return nil;
|
||||
}
|
||||
break;
|
||||
}
|
||||
for(m = milabbrev; m->abbr != nil; m++)
|
||||
if(strncmp(s, m->abbr, strlen(m->abbr)) == 0)
|
||||
break;
|
||||
if(m->abbr != nil){
|
||||
snprint(tm->zone, sizeof(tm->zone), "%s", m->abbr);
|
||||
tzo = m->off;
|
||||
break;
|
||||
}
|
||||
/* fall through */
|
||||
case 1:
|
||||
/* offset: [+-]hhmm */
|
||||
q = s;
|
||||
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);
|
||||
break;
|
||||
}
|
||||
if(w != -1)
|
||||
goto baddate;
|
||||
s = q;
|
||||
/* fall through */
|
||||
case 2:
|
||||
/* offset: [+-]hh:mm */
|
||||
z0 = getnum(&s, 2, &ok);
|
||||
if(*s++ != ':')
|
||||
goto baddate;
|
||||
z1 = getnum(&s, 2, &ok);
|
||||
if(z1 > 60)
|
||||
goto baddate;
|
||||
tzo = zs*(3600*z0 + 60*z1);
|
||||
snprint(tm->zone, sizeof(tm->zone), "%c%d02:%02d", zs<0?'-':'+', z0, z1);
|
||||
break;
|
||||
}
|
||||
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;
|
||||
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 '\t':
|
||||
if(*s != ' ' && *s != '\t' && *s != ',')
|
||||
goto baddate;
|
||||
while(*p == ' ' || *p == '\t' || *p == ',')
|
||||
p++;
|
||||
while(*s == ' ' || *s == '\t' || *s == ',')
|
||||
s++;
|
||||
break;
|
||||
default:
|
||||
if(*s == 0)
|
||||
goto baddate;
|
||||
if(*s++ != c0)
|
||||
goto baddate;
|
||||
break;
|
||||
}
|
||||
if(!ok)
|
||||
goto baddate;
|
||||
}
|
||||
|
||||
if(!sloppy && ampm != -1 && tm->hour > 12)
|
||||
goto baddate;
|
||||
if(ampm == 1)
|
||||
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;
|
||||
if(tm->mday < 0 || tm->mday > mdays[tm->mon])
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
tmnorm(tm);
|
||||
tm->tzoff = tzo;
|
||||
if(!zoned)
|
||||
getzoneoff(tz, tm->abs, tm);
|
||||
else if(zparsed != nil)
|
||||
getzoneoff(zparsed, tm->abs, tm);
|
||||
tm->abs -= tm->tzoff;
|
||||
if(tz != nil || !zoned)
|
||||
tmtime(tm, tm->abs, tz);
|
||||
return tm;
|
||||
baddate:
|
||||
werrstr("invalid date %s near '%s'", str, s);
|
||||
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, nil);
|
||||
return dotmfmt(&f, tf);
|
||||
}
|
|
@ -20,6 +20,7 @@ CFILES=\
|
|||
cleanname.c\
|
||||
crypt.c\
|
||||
ctype.c\
|
||||
date.c\
|
||||
encodefmt.c\
|
||||
execl.c\
|
||||
exits.c\
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue