OK, turing.

<- leave blank

Thu Jul 9 14:11:53 EDT 2020

<a href=http://1541.ru/cms/crowd1.php>Проект N1 Crowd1 - Нас уже 6
миллионов!  Присоединяйтесь.  Активный и пассивный заработок.  Мы в Alexa на 2-м
месте А ген.  директор орифлэйма Johan Westerdahl перешел к нам в
проект!</a>

Thu Jul 9 09:38:41 EDT 2020
Revert "Revert "Renumber harvey system call numbers""

Wed Jul 8 17:02:54 EDT 2020
fgfdfg

Wed Jul 8 08:06:40 EDT 2020
#!/bin/sh

set -e

killdmapds() {
	kill -9 $(cat $tdir/music.pid)
	kill -9 $(cat $tdir/music-wav.pid)
	kill -9 $(cat $tdir/music-mp3.pid)
	kill -9 $(cat $tdir/music-rt.pid)
	kill -9 $(cat $tdir/movies.pid)
	kill -9 $(cat $tdir/pictures.pid)
}

printerrors() {
	[ -f $tdir/errors ] && cat $tdir/errors >&2
}

error() {
	echo ERROR: $*
	exit 1
}

test1() {
	# 1.  Do some stress testing:
	echo -n TESTING dmapd under stress...
	../dmapd/src/dmapd-stress-test -i 1 -s music-test >/dev/null
	2>>$tdir/errors
	../dmapd/src/dmapd-stress-test -i 1 -s music-test-wav >/dev/null
	2>>$tdir/errors
	../dmapd/src/dmapd-stress-test -i 1 -s music-test-mp3 >/dev/null
	2>>$tdir/errors
	../dmapd/src/dmapd-stress-test -i 1 -s music-test-rt >/dev/null
	2>>$tdir/errors
	../dmapd/src/dmapd-stress-test -i 1 -s movies-test >/dev/null
	2>>$tdir/errors
	../dmapd/src/dmapd-stress-test -i 1 -s pictures-test >/dev/null
	2>>$tdir/errors
	echo OK
}

test2() {
	# 2.  Test generating cache record.
	echo -n TESTING dmapd generating a cache record...
	cp $d/music/willie.ogg $tdir/music/willie.ogg
	properhash=$(dmapd-hashgen $tdir/music/willie.ogg)
	dmapd -f -m $tdir/music -d $tdir/db -x >/dev/null 2>>$tdir/errors
	[ -f $tdir/db/DAAP/$properhash.record ] || error failed to generate cache
	record.
	echo OK
}

test3() {
	# 3.  Test removing cache record because media file changed.
	echo -n TESTING that dmapd removes an out-of-date cache record if a file
	changes...
	cp $d/music/willie.ogg $tdir/music/willie.ogg
	properhash=$(dmapd-hashgen $tdir/music/willie.ogg)
	dmapd -f -m $tdir/music -d $tdir/db -x >/dev/null 2>>$tdir/errors
	# NOTE: dmapd used to check if the media file hash changed since
	# generating the cache file.  This was a bit slow, so now dmapd
	# merely checks to see if the media file's mtime > cache file's mtime.
	# Sleep here to make sure the mtimes differ.
	sleep 2
	cp $d/music/acdc.mp3 $tdir/music/willie.ogg
	corrupthash=$(dmapd-hashgen $tdir/music/willie.ogg)
	dmapd -f -m $tdir/music -d $tdir/db -x >/dev/null 2>>$tdir/errors
	[ -f $tdir/db/DAAP/$properhash.record ] && error failed to remove
	out-of-date cache record.
	[ -f $tdir/db/DAAP/$corrupthash.record ] || error failed to generate cache
	record.
	echo OK
}

test4() {
	# 4.  Test removing cache record because media file missing.
	echo -n TESTING that dmapd removes an out-of-date cache record if a file
	disappears...
	cp $d/music/willie.ogg $tdir/music/willie.ogg
	properhash=$(dmapd-hashgen $tdir/music/willie.ogg)
	dmapd -f -m $tdir/music -d $tdir/db -x >/dev/null 2>>$tdir/errors
	[ -f $tdir/db/DAAP/$properhash.record ] || error failed to generate cache
	record.
	rm -f $tdir/music/willie.ogg
	dmapd -f -m $tdir/music -d $tdir/db -x >/dev/null 2>>$tdir/errors
	[ -f $tdir/db/DAAP/$properhash.record ] && error failed to remove
	out-of-date cache record.
	echo OK
}

test5() {
	# 5.  Test that we are able to load the big music library.
	echo -n TESTING that dmapd loads the big music library...
	[ -d /var/db/BigDisk-local/Storage/Music/ ] || error
	/var/db/BigDisk-local/Storage/Music does not exist.
	dmapd -f -m /var/db/BigDisk-local/Storage/Music/ -d $tdir/db -x
	>/dev/null 2>>$tdir/errors
	[ $?  != 0 ] && error failed to identify at least one duration
	echo OK
}

tdir=$(mktemp -d)
u=$(id -u -n)
g=$(id -g -n)
d=$(pwd)

which dmapd >/dev/null 2>&1 || error dmapd not installed

trap "killdmapds; printerrors; /bin/rm -rf $tdir" SIGTERM SIGINT EXIT

export DMAPD_DEBUG=y
export LIBDMAPSHARING_ENABLE_LOCAL=y

mkdir $tdir/music
mkdir -p $tdir/db/DAAP

# Start up a few servers for the tests.
dmapd -R $tdir -m $d/music -n music-test -i $tdir/music.pid -l $tdir/music.lock -u
$u -g $g >/dev/null 2>&1 || error "Could not run dmapd"
dmapd -R $tdir -m $d/music -n music-test-wav -i $tdir/music-wav.pid -l
$tdir/music-wav.lock -u $u -g $g -t audio/wav -d $tdir >/dev/null 2>&1 ||
error "Could not run dmapd"
dmapd -R $tdir -m $d/music -n music-test-mp3 -i $tdir/music-mp3.pid -l
$tdir/music-mp3.lock -u $u -g $g -t audio/mp3 -d $tdir >/dev/null 2>&1 ||
error "Could not run dmapd"
dmapd -R $tdir -m $d/music -n music-test-rt -i $tdir/music-rt.pid -l
$tdir/music-tr.lock -u $u -g $g -t audio/mp3 -r >/dev/null 2>&1 || error
"Could not run dmapd"
dmapd -R $tdir -m $d/movies -n movies-test -i $tdir/movies.pid -l
$tdir/movies.lock -u $u -g $g >/dev/null 2>&1 || error "Could not run dmapd"
dmapd -R $tdir -p $d/pictures -n pictures-test -i $tdir/pictures.pid -l
$tdir/pictures.lock -u $u -g $g >/dev/null 2>&1 || error "Could not run
dmapd"

if [ 0 = $# ]; then
	tests="1 2 3 4 5"
else
	tests="$*"
fi

for i in $tests; do
	test$i
done

# Got this far without a real error.
rm $tdir/errors


Tue Jul 7 23:33:21 EDT 2020
#include <u.h>
#include <libc.h>

typedef struct Tzabbrev Tzabbrev;
typedef struct Tzoffpair Tzoffpair;

#define Ctimefmt "W 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, "%*.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 '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);
				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 '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;
	}

	/*
	 * 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, nil);
	return dotmfmt(&f, tf);
}



Tue Jul 7 21:40:34 EDT 2020
Authentication-Results: minnie.tuhs.org;
	dkim=pass (2048-bit key; unprotected) header.d=gmail.com
	header.i=@gmail.com header.b="TCKVQwNt";
	dkim-atps=neutral
From: Rob Pike <robpike@gmail.com>
Date: Wed, 8 Jul 2020 08:47:38 +1000
To: Norman Wilson <norman@oclsc.org>
Subject: Re: [TUHS] v7 uucp debugging help requested
Cc: The Eunuchs Hysterical Society <tuhs@tuhs.org>
Errors-To: tuhs-bounces@minnie.tuhs.org
Sender: "TUHS" <tuhs-bounces@minnie.tuhs.org>

The thing about ed that is missed by johnny-come-latelys who mock it for
its lowness is that it was so much higher than most, if not all the
commercially available text editors of its time.  The gold standard was
perhaps Son of Stopgap, whose very name tells you the state of things.

I remember spending a couple of days baking inside a radio telescope
control cabin implementing a miniature version of ed in forth just so I
work more effectively on the task to come.

-rob


On Wed, Jul 8, 2020 at 5:22 AM Norman Wilson <norman@oclsc.org> wrote:

> Dave Horsfall:
>
> A boss of mine insisted that we all learned "ed", because one day it
> might
> be the only editor available to you after a crash; he was right...
>
> ====
>
> Besides which, as The Blessed Manual said in every
> Research edition:
> ed is the standard text editor.
>
> Norman Wilson
> Toronto ON
> (typing this in Toronto qed)
>




Tue Jul 7 21:40:28 EDT 2020
From: Norman Wilson <norman@oclsc.org>
To: tuhs@tuhs.org
Date: Tue, 07 Jul 2020 15:20:58 -0400
Subject: Re: [TUHS] v7 uucp debugging help requested
Errors-To: tuhs-bounces@minnie.tuhs.org
Sender: "TUHS" <tuhs-bounces@minnie.tuhs.org>

Dave Horsfall:

  A boss of mine insisted that we all learned "ed", because one day it might
  be the only editor available to you after a crash; he was right...

====

Besides which, as The Blessed Manual said in every
Research edition:
	ed is the standard text editor.

Norman Wilson
Toronto ON
(typing this in Toronto qed)



Tue Jul 7 21:40:21 EDT 2020
To: tuhs@tuhs.org
Date: Tue, 7 Jul 2020 12:16:13 -0400 (EDT)
From: norman@oclsc.org (Norman Wilson)
Subject: Re: [TUHS] Topics...
Errors-To: tuhs-bounces@minnie.tuhs.org
Sender: "TUHS" <tuhs-bounces@minnie.tuhs.org>

John Cowan:

  Very much +1.  Part of the trouble is that Gmail and similar clients don't
  routinely show you the Subject: line to make it easy to edit it; you have
  to take affirmative action when you want to change the subject matter.

====

Perhaps we should take a leaf from 1980s Rob Pike, and
just automatically change every message to be

Subject: The content of this message

(There is an actual story behind that, but I'll leave it
to Rob to tell.)

Norman Wilson
Toronto ON



Tue Jul 7 21:01:15 EDT 2020
.TH TMDATE 2
.SH NAME
tmnow, tmgetzone, 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 *tmgetzone(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 as 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
Tmgetzone 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
.I ep
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
The day of week in 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, Z, ZZ
The timezone in named, [+-]HHMM and [+-]HH:MM 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,RFC-3339 and RFC-2822 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 = tmgetzone("local") == nil)
	sysfatal("load zone: %r");
if((zp = tmgetzone("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.

.IP
.EX
Tm a, b;

tmparse(&a, nil, "Tue Dec 10 12:36:00 PST 2019");
tmparse(&b, nil, "Tue Dec 10 15:36:00 EST 2019");
if(a.abs == b.abs)
	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 = tmgetzone("local")) == nil)
	sysfatal("load zone: %r");
if((zp = tmgetzone("US_Pacific")) == nil)
	sysfatal("load zone: %r");
if(tmnow(&here, zl) == nil)
	sysfatal("get time: %r");
if(tmtime(&there, here.abs, zp) == nil)
	sysfatal("shift time: %r");
.EE

.PP
Add a day to two times.  Because we picked daylight savings
time to adjust over, only 23 hours are added.

.EX
Tm t;
tmparse(&t, "W MMM D hh:mm:ss z YYYY, "Sun Nov 2 13:11:11 PST 2019");
tm.day++;
tmrecalc(&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.


Tue Jul 7 15:05:46 EDT 2020
#include <u.h>
#include <libc.h>

typedef struct Tzabbrev Tzabbrev;
typedef struct Tzoffpair Tzoffpair;

#define Ctimefmt "W 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, "%*.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 't':
			v = tm->nsec / (1000*1000);
			print("tm->nsec: %d, v: %d\n", tm->nsec, v);
			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;
			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, tm->nsec%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%(1000*1000)); 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);
				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 't':
			switch(w){
			case -1:
			case 1:
			case 2:
			case 3:
				tm->nsec += getnum(&s, 3, &ok)*1000*1000;
				print("parse-thousands: %d %d\n", tm->nsec,
				ok);
				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;
				}
				/* 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;
	}

	/*
	 * 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, nil);
	return dotmfmt(&f, tf);
}



Tue Jul 7 12:39:40 EDT 2020
Внимание!  Создана новая модификация 3D БОТОВ для сайтов и ЧАТ ботов, теперь они
визуальные - почти живые, работают 24/7 и БЕСПЛАТНО.  Зарабатывают для Вас деньги
в Интернет на полном АВТОМАТЕ и полуавтомате.  Цена: вопроса от 270$ примеры здесь

next