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:
parent
3ba1d83d20
commit
56e869ac70
6 changed files with 1329 additions and 488 deletions
|
@ -314,22 +314,44 @@ extern double fmod(double, double);
|
|||
/*
|
||||
* Time-of-day
|
||||
*/
|
||||
typedef struct Tzone Tzone;
|
||||
#pragma incomplete Tzone
|
||||
|
||||
|
||||
typedef
|
||||
struct Tm
|
||||
{
|
||||
int sec;
|
||||
int min;
|
||||
int hour;
|
||||
int mday;
|
||||
int mon;
|
||||
int year;
|
||||
int wday;
|
||||
int yday;
|
||||
char zone[4];
|
||||
int tzoff;
|
||||
int nsec; /* nseconds (range 0...1e9) */
|
||||
int sec; /* seconds (range 0..60) */
|
||||
int min; /* minutes (0..59) */
|
||||
int hour; /* hours (0..23) */
|
||||
int mday; /* day of the month (1..31) */
|
||||
int mon; /* month of the year (0..11) */
|
||||
int year; /* year A.D. */
|
||||
int wday; /* day of week (0..6, Sunday = 0) */
|
||||
int yday; /* day of year (0..365) */
|
||||
char zone[16]; /* time zone name */
|
||||
int tzoff; /* time zone delta from GMT */
|
||||
Tzone *tz; /* time zone associated with this date */
|
||||
} Tm;
|
||||
|
||||
typedef
|
||||
struct Tmfmt {
|
||||
char *fmt;
|
||||
Tm *tm;
|
||||
} Tmfmt;
|
||||
|
||||
#pragma varargck type "τ" Tmfmt
|
||||
|
||||
extern Tzone* tzload(char *name);
|
||||
extern Tm* tmnow(Tm*, Tzone*);
|
||||
extern Tm* tmtime(Tm*, vlong, Tzone*);
|
||||
extern Tm* tmtimens(Tm*, vlong, int, Tzone*);
|
||||
extern Tm* tmparse(Tm*, char*, char*, Tzone*, char **ep);
|
||||
extern vlong tmnorm(Tm*);
|
||||
extern Tmfmt tmfmt(Tm*, char*);
|
||||
extern void tmfmtinstall(void);
|
||||
|
||||
extern Tm* gmtime(long);
|
||||
extern Tm* localtime(long);
|
||||
extern char* asctime(Tm*);
|
||||
|
|
275
sys/man/2/tmdate
Normal file
275
sys/man/2/tmdate
Normal file
|
@ -0,0 +1,275 @@
|
|||
.TH TMDATE 2
|
||||
.SH NAME
|
||||
tmnow, tzload, tmtime, tmparse, tmfmt, tmnorm - convert date and time
|
||||
.SH SYNOPSIS
|
||||
.B #include <u.h>
|
||||
.br
|
||||
.B #include <libc.h>
|
||||
.PP
|
||||
.ft L
|
||||
.nf
|
||||
.EX
|
||||
typedef struct Tmd Tmd;
|
||||
typedef struct Tmfmt Tmfmt;
|
||||
|
||||
struct {
|
||||
int nsec; /* nanoseconds (range 0..1e9) */
|
||||
int sec; /* seconds (range 0..59) */
|
||||
int min; /* minutes (0..59) */
|
||||
int hour; /* hours (0..23) */
|
||||
int mday; /* day of the month (1..31) */
|
||||
int mon; /* month of the year (0..11) */
|
||||
int year; /* C.E year - 1900 */
|
||||
int wday; /* day of week (0..6, Sunday = 0) */
|
||||
int yday; /* day of year (0..365) */
|
||||
char zone[]; /* time zone name */
|
||||
int tzoff; /* time zone delta from GMT, seconds */
|
||||
};
|
||||
|
||||
Tzone *tzload(char *name);
|
||||
Tm *tmnow(Tm *tm, char *tz);
|
||||
Tm *tmtime(Tm *tm, vlong abs, Tzone *tz);
|
||||
Tm *tmtimens(Tm *tm, vlong abs, int ns, Tzone *tz);
|
||||
Tm *tmparse(Tm *dst, char *fmt, char *tm, Tzone *zone, char **ep);
|
||||
vlong tmnorm(Tm *tm);
|
||||
Tmfmt tmfmt(Tm *tm, char *fmt);
|
||||
void tmfmtinstall(void);
|
||||
.EE
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
This family of functions handles simple date and time manipulation.
|
||||
.PP
|
||||
Time zones are loaded by name.
|
||||
They can be specified as the abbreviated timezone name,
|
||||
the full timezone name, the path to a timezone file,
|
||||
or an absolute offset in the HHMM form.
|
||||
.PP
|
||||
When given as a timezone, any instant-dependent adjustments such as leap
|
||||
seconds and daylight savings time will be applied to the derived fields of
|
||||
struct Tm, but will not affect the absolute time.
|
||||
The time zone name local always refers to the time in /env/timezone.
|
||||
The nil timezone always refers to GMT.
|
||||
.PP
|
||||
Tzload loads a timezone by name. The returned timezone is
|
||||
cached for the lifetime of the program, and should not be freed.
|
||||
Loading a timezone repeatedly by name loads from the cache, and
|
||||
does not leak.
|
||||
.PP
|
||||
Tmnow gets the current time of day in the requested time zone.
|
||||
.PP
|
||||
Tmtime converts the millisecond-resolution timestamp 'abs'
|
||||
into a Tm struct in the requested timezone.
|
||||
Tmtimens does the same, but with a nanosecond accuracy.
|
||||
.PP
|
||||
Tmstime is identical to tmtime, but accepts the time in sec-
|
||||
onds.
|
||||
.PP
|
||||
Tmparse parses a time from a string according to the format argument.
|
||||
The point at which the parsing stopped is returned in
|
||||
.IR ep .
|
||||
If
|
||||
.I ep
|
||||
is nil, trailing garbage is ignored.
|
||||
The result is returned in the timezone requested.
|
||||
If there is a timezone in the date, and a timezone is provided
|
||||
when parsing, then the zone is shifted to the provided timezone.
|
||||
Parsing is case-insensitive
|
||||
.PP
|
||||
The format argument contains zero or more of the following components:
|
||||
.TP
|
||||
.B Y, YY, YYYY
|
||||
Represents the year.
|
||||
.I YY
|
||||
prints the year in 2 digit form.
|
||||
.TP
|
||||
.B M, MM, MMM, MMMM
|
||||
The month of the year, in unpadded numeric, padded numeric, short name, or long name,
|
||||
respectively.
|
||||
.TP
|
||||
.B D, DD
|
||||
The day of month in unpadded or padded numeric form, respectively.
|
||||
.TP
|
||||
.B W, WW, WWW
|
||||
The day of week in numeric, short or long name form, respectively.
|
||||
.TP
|
||||
.B h, hh
|
||||
The hour in unpadded or padded form, respectively
|
||||
.TP
|
||||
.B m, mm
|
||||
The minute in unpadded or padded form, respectively
|
||||
.TP
|
||||
.B s, ss
|
||||
The second in unpadded or padded form, respectively
|
||||
.TP
|
||||
.B t, tt
|
||||
The milliseconds in unpadded and padded form, respectively.
|
||||
.B u, uu, uuu, uuuu
|
||||
The microseconds in unpadded. padded form modulo milliseconds,
|
||||
or unpadded, padded forms of the complete value, respectively.
|
||||
.B n, nn, nnn, nnnn, nnnnn, nnnnnn
|
||||
The nanoseconds in unpadded and padded form modulo milliseconds,
|
||||
the unpadded and padded form modulo microseconds,
|
||||
and the unpadded and padded complete value, respectively.
|
||||
.TP
|
||||
.B Z, ZZ, ZZZ
|
||||
The timezone in [+-]HHMM and [+-]HH:MM, and named form, respectively.
|
||||
.TP
|
||||
.B a, A
|
||||
Lower and uppercase 'am' and 'pm' specifiers, respectively.
|
||||
.TP
|
||||
.B [...]
|
||||
Quoted text, copied directly to the output.
|
||||
.TP
|
||||
.B _
|
||||
When formatting, this inserts padding into the date format.
|
||||
The padded width of a field is the sum of format and specifier
|
||||
characters combined. When
|
||||
For example,
|
||||
.I __h
|
||||
will format to a width of 3. When parsing, this acts as whitespace.
|
||||
.TP
|
||||
.B ?
|
||||
When parsing, this makes the following argument match fuzzily.
|
||||
Fuzzy matching means that all formats are tried, from most to least specific.
|
||||
For example,
|
||||
.I ?M
|
||||
will match
|
||||
.IR January ,
|
||||
.IR Jan ,
|
||||
.IR 01 ,
|
||||
and
|
||||
.IR 1 ,
|
||||
in that order of preference.
|
||||
.TP
|
||||
.B ~
|
||||
When parsing a date, this slackens range enforcement, accepting
|
||||
out of range values such as January
|
||||
.IR 32 ,
|
||||
which would get normalized to February 1st.
|
||||
.PP
|
||||
Any characters not specified above are copied directly to output,
|
||||
without modification.
|
||||
|
||||
|
||||
|
||||
.PP
|
||||
If the format argument is nil, it makes an
|
||||
attempt to parse common human readable date formats. These
|
||||
formats include ISO-8601, RFC3339 and RFC2822 dates.
|
||||
.
|
||||
.PP
|
||||
Tmfmt produces a format description structure suitable for passing
|
||||
to
|
||||
.IR fmtprint (2) .
|
||||
If fmt is nil, we default to the format used in
|
||||
.IR ctime (2).
|
||||
The format of the format string is identical to
|
||||
.IR tmparse.
|
||||
|
||||
.PP
|
||||
When parsing, any amount of whitespace is treated as a single token.
|
||||
All string matches are case insensitive, and zero padding is optional.
|
||||
|
||||
.PP
|
||||
Tmnorm takes a manually adjusted Tm structure, and normalizes it,
|
||||
returning the absolute timestamp that the date represents.
|
||||
Normalizing recomputes the
|
||||
.I year, mon, mday, hr, min, sec
|
||||
and
|
||||
.I tzoff
|
||||
fields.
|
||||
If
|
||||
.I tz
|
||||
is non-nil, then
|
||||
.I tzoff
|
||||
will be recomputed, taking into account daylight savings
|
||||
for the absolute time.
|
||||
The values not used in the computation are recomputed for
|
||||
the resulting absolute time.
|
||||
All out of range values are wrapped.
|
||||
For example, December 32 will roll over to Jan 1 of the
|
||||
following year.
|
||||
.PP
|
||||
Tmfmtinstall installs a time format specifier %τ. The time
|
||||
format behaves as in tmfmt
|
||||
|
||||
.SH EXAMPLES
|
||||
.PP
|
||||
All examples assume tmfmtinstall has been called.
|
||||
.PP
|
||||
Get the current date in the local timezone, UTC, and
|
||||
US_Pacific time. Print it using the default format.
|
||||
|
||||
.IP
|
||||
.EX
|
||||
Tm t;
|
||||
Tzone *zl, *zp;
|
||||
if((zl = tzload("local") == nil)
|
||||
sysfatal("load zone: %r");
|
||||
if((zp = tzload("US_Pacific") == nil)
|
||||
sysfatal("load zone: %r");
|
||||
print("local: %τ\\n", tmfmt(tmnow(&t, zl), nil));
|
||||
print("gmt: %τ\\n", tmfmt(tmnow(&t, nil), nil));
|
||||
print("eastern: %τ\\n", tmfmt(tmnow(&t, zp), nil));
|
||||
.EE
|
||||
.PP
|
||||
Compare if two times are the same, regardless of timezone.
|
||||
Done with full, strict error checking.
|
||||
|
||||
.IP
|
||||
.EX
|
||||
Tm a, b;
|
||||
|
||||
if(tmparse(&a, nil, "Tue Dec 10 12:36:00 PST 2019", &e) == nil)
|
||||
sysfatal("failed to parse: %r");
|
||||
if(*e != '\0')
|
||||
sysfatal("trailing junk %s", e);
|
||||
if(tmparse(&b, nil, "Tue Dec 10 15:36:00 EST 2019", &e) == nil)
|
||||
sysfata("failed to parse: %r");
|
||||
if(*e != '\0')
|
||||
sysfatal("trailing junk %s", e);
|
||||
if(tmnorm(a) == tmnorm(b) && a.nsec == b.nsec)
|
||||
print("same\\n");
|
||||
else
|
||||
print("different\\n");
|
||||
.EE
|
||||
|
||||
.PP
|
||||
Convert from one timezone to another.
|
||||
|
||||
.IP
|
||||
.EX
|
||||
Tm here, there;
|
||||
Tzone *zl, *zp;
|
||||
if((zl = tzload("local")) == nil)
|
||||
sysfatal("load zone: %r");
|
||||
if((zp = tzload("US_Pacific")) == nil)
|
||||
sysfatal("load zone: %r");
|
||||
if(tmnow(&here, zl) == nil)
|
||||
sysfatal("get time: %r");
|
||||
if(tmtime(&there, tmnorm(&tm), zp) == nil)
|
||||
sysfatal("shift time: %r");
|
||||
.EE
|
||||
|
||||
.PP
|
||||
Add a day. Because cross daylight savings, only 23 hours are added.
|
||||
|
||||
.EX
|
||||
Tm t;
|
||||
char *date = "Sun Nov 2 13:11:11 PST 2019";
|
||||
if(tmparse(&t, "W MMM D hh:mm:ss z YYYY, date, &e) == nil)
|
||||
print("failed top parse");
|
||||
tm.day++;
|
||||
tmnorm(&t);
|
||||
print("%τ", &t); /* Mon Nov 3 13:11:11 PST 2019 */
|
||||
.EE
|
||||
|
||||
.SH BUGS
|
||||
.PP
|
||||
There is no way to format specifier for subsecond precision.
|
||||
.PP
|
||||
The timezone information that we ship is out of date.
|
||||
.PP
|
||||
The Plan 9 timezone format has no way to express leap seconds.
|
||||
.PP
|
||||
We provide no way to manipulate timezones.
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
996
sys/src/libc/port/date.c
Normal 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);
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ CFILES=\
|
|||
cleanname.c\
|
||||
crypt.c\
|
||||
ctype.c\
|
||||
date.c\
|
||||
encodefmt.c\
|
||||
execl.c\
|
||||
exits.c\
|
||||
|
|
Loading…
Reference in a new issue