ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/daytime.b/
implement Daytime;
#
# These routines convert time as follows:
#
# The epoch is 0000 Jan 1 1970 GMT.
# The argument time is in microseconds since then.
# The local(t) entry returns a reference to an ADT
# containing
#
# seconds (0-59)
# minutes (0-59)
# hours (0-23)
# day of month (1-31)
# month (0-11)
# year-1900
# weekday (0-6, Sun is 0)
# day of the year
# daylight savings flag
#
# The routine gets the daylight savings time from the file /locale/timezone.
#
# text(tvec)
# where tvec is produced by local
# returns a string that has the time in the form
#
# Thu Jan 01 00:00:00 GMT 1970n0
# 012345678901234567890123456789
# 0 1 2
#
# time() just reads the time from /dev/time
# and then calls localtime, then asctime.
#
# The sign bit of second times will turn on 68 years from the epoch ->2038
#
include "sys.m";
include "string.m";
include "daytime.m";
S: String;
sys: Sys;
dmsize := array[] of {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
};
ldmsize := array[] of {
31, 29, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31
};
Timezone: adt
{
stname: string;
dlname: string;
stdiff: int;
dldiff: int;
dlpairs: array of int;
};
timezone: ref Timezone;
now(): int
{
if(sys == nil)
sys = load Sys Sys->PATH;
fd := sys->open("/dev/time", sys->OREAD);
if(fd == nil)
return 0;
buf := array[128] of byte;
n := sys->read(fd, buf, len buf);
if(n < 0)
return 0;
t := (big string buf[0:n]) / big 1000000;
return int t;
}
time(): string
{
t := now();
tm := local(t);
return text(tm);
}
local(tim: int): ref Tm
{
ct: ref Tm;
if(timezone == nil)
timezone = readtimezone(nil);
t := tim + timezone.stdiff;
dlflag := 0;
for(i := 0; i+1 < len timezone.dlpairs; i += 2) {
if(t >= timezone.dlpairs[i] && t < timezone.dlpairs[i+1]) {
t = tim + timezone.dldiff;
dlflag++;
break;
}
}
ct = gmt(t);
if(dlflag) {
ct.zone = timezone.dlname;
ct.tzoff = timezone.dldiff;
}
else {
ct.zone = timezone.stname;
ct.tzoff = timezone.stdiff;
}
return ct;
}
gmt(tim: int): ref Tm
{
xtime := ref Tm;
# break initial number into days
hms := tim % 86400;
day := tim / 86400;
if(hms < 0) {
hms += 86400;
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 + 7340036) % 7;
# year number
if(day >= 0)
for(d1 = 70; day >= dysize(d1+1900); d1++)
day -= dysize(d1+1900);
else
for (d1 = 70; day < 0; d1--)
day += dysize(d1+1900-1);
xtime.year = d1;
d0 := day;
xtime.yday = d0;
# generate month
if(dysize(d1+1900) == 366)
dmsz := ldmsize;
else
dmsz = dmsize;
for(d1 = 0; d0 >= dmsz[d1]; d1++)
d0 -= dmsz[d1];
xtime.mday = d0 + 1;
xtime.mon = d1;
xtime.zone = "GMT";
xtime.tzoff = 0;
return xtime;
}
wkday := array[] of {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
weekday := array[] of {
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
};
month := array[] of {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
text(t: ref Tm): string
{
if(sys == nil)
sys = load Sys Sys->PATH;
year := 1900+t.year;
return sys->sprint("%s %s %.2d %.2d:%.2d:%.2d %s %d",
wkday[t.wday],
month[t.mon],
t.mday,
t.hour,
t.min,
t.sec,
t.zone,
year);
}
filet(now: int, file: int): string
{
if(sys == nil)
sys = load Sys Sys->PATH;
t := local(file);
if(now - file < 6*30*24*3600)
return sys->sprint("%s %.2d %.2d:%.2d",
month[t.mon], t.mday, t.hour, t.min);
year := 1900+t.year;
return sys->sprint("%s %.2d %d", month[t.mon], t.mday, year);
}
dysize(y: int): int
{
if(y%4 == 0 && (y%100 != 0 || y%400 == 0))
return 366;
return 365;
}
readtimezone(fname: string): ref Timezone
{
if(sys == nil)
sys = load Sys Sys->PATH;
tz := ref Timezone;
tz.stdiff = 0;
tz.stname = "GMT";
s: string;
if(fname == nil){
s = readfile("/env/timezone");
if(s == nil)
s = readfile("/locale/timezone");
}else{
if(fname[0] != '/' && fname[0] != '#')
fname = "/locale/" + fname;
s = readfile(fname);
}
if(s == nil)
return tz;
if(s[0] == '/' || s[0] == '#'){
if(s[len s-1] == '\n')
s = s[0: len s-1];
s = readfile(s);
if(s == nil)
return tz;
}
(n, val) := sys->tokenize(s, "\t \n\r");
if(n < 4)
return tz;
tz.stname = hd val;
val = tl val;
tz.stdiff = int hd val;
val = tl val;
tz.dlname = hd val;
val = tl val;
tz.dldiff = int hd val;
val = tl val;
tz.dlpairs = array[n-4] of {* => 0};
for(j := 0; val != nil; val = tl val)
tz.dlpairs[j++] = int hd val;
return tz;
}
readfile(name: string): string
{
fd := sys->open(name, Sys->OREAD);
if(fd == nil)
return nil;
buf := array[2048] of byte;
n := sys->read(fd, buf, len buf);
if(n <= 0)
return nil;
return string buf[0:n];
}
SEC2MIN: con 60;
SEC2HOUR: con 60*SEC2MIN;
SEC2DAY: con 24*SEC2HOUR;
tm2epoch(tm: ref Tm): int
{
secs := 0;
#
# seconds per year
#
yr := tm.year + 1900;
if(yr < 1970)
for(i := yr; i < 1970; i++)
secs -= dysize(i) * SEC2DAY;
else
for(i = 1970; i < yr; i++)
secs += dysize(i) * SEC2DAY;
#
# seconds per month
#
if(dysize(yr) == 366)
dmsz := ldmsize;
else
dmsz = dmsize;
for(i = 0; i < tm.mon; i++)
secs += dmsz[i] * SEC2DAY;
#
# secs in last month
#
secs += (tm.mday-1) * SEC2DAY;
#
# hours, minutes, seconds
#
secs += tm.hour * SEC2HOUR;
secs += tm.min * SEC2MIN;
secs += tm.sec;
#
# time zone offset includes daylight savings time
#
return secs - tm.tzoff;
}
# handle three formats (we'll be a bit more tolerant)
# Sun, 06 Nov 1994 08:49:37 TZ (rfc822+rfc1123)
# Sunday, 06-Nov-94 08:49:37 TZ (rfc850, obsoleted by rfc1036)
# Sun Nov 6 08:49:37 1994 (ANSI C's asctime() format, assume GMT)
#
# return nil on parsing error
#
string2tm(date: string): ref Tm
{
buf: string;
ok: int;
tm := ref Tm;
if(S == nil)
S = load String String->PATH;
# Weekday|Wday
(date, buf) = dateword(date);
tm.wday = strlookup(wkday, buf);
if(tm.wday < 0)
tm.wday = strlookup(weekday, buf);
if(tm.wday < 0)
return nil;
# Try Mon
odate := date;
(date, buf) = dateword(date);
tm.mon = strlookup(month, buf);
if(tm.mon >= 0) {
# Mon was OK, so asctime() format
# DD
(date, tm.mday) = datenum(date);
if(tm.mday < 1 || tm.mday > 31)
return nil;
# HH:MM:SS
(ok, date) = hhmmss(date, tm);
if(!ok)
return nil;
# optional time zone
while(date != nil && date[0] == ' ')
date = date[1:];
if(date != nil && !(date[0] >= '0' && date[0] <= '9')){
for(i := 0; i < len date; i++)
if(date[i] == ' '){
(tm.zone, tm.tzoff) = tzinfo(date[0: i]);
date = date[i:];
break;
}
}
# YY|YYYY
(nil, tm.year) = datenum(date);
if(tm.year > 1900)
tm.year -= 1900;
if(tm.zone == ""){
tm.zone = "GMT";
tm.tzoff = 0;
}
} else {
# Mon was not OK
date = odate;
# DD Mon YYYY or DD-Mon-(YY|YYYY)
(date, tm.mday) = datenum(date);
if(tm.mday < 1 || tm.mday > 31)
return nil;
(date, buf) = dateword(date);
tm.mon = strlookup(month, buf);
if(tm.mon < 0 || tm.mon >= 12)
return nil;
(date, tm.year) = datenum(date);
if(tm.year > 1900)
tm.year -= 1900;
# HH:MM:SS
(ok, buf) = hhmmss(date, tm);
if(!ok)
return nil;
(tm.zone, tm.tzoff) = tzinfo(buf);
if(tm.zone == "")
return nil;
}
return tm;
}
dateword(date: string): (string, string)
{
notalnum: con "^A-Za-z0-9";
date = S->drop(date, notalnum);
(w, rest) := S->splitl(date, notalnum);
return (rest, w);
}
datenum(date: string): (string, int)
{
notdig: con "^0-9";
date = S->drop(date, notdig);
(num, rest) := S->splitl(date, notdig);
return (rest, int num);
}
strlookup(a: array of string, s: string): int
{
n := len a;
for(i := 0; i < n; i++) {
if(s == a[i])
return i;
}
return -1;
}
hhmmss(date: string, tm: ref Tm): (int, string)
{
err := (0, "");
(date, tm.hour) = datenum(date);
if(tm.hour < 0 || tm.hour >= 24)
return err;
(date, tm.min) = datenum(date);
if(tm.min < 0 || tm.min >= 60)
return err;
(date, tm.sec) = datenum(date);
if(tm.sec < 0 || tm.sec >= 60)
return err;
return (1, date);
}
tzinfo(tz: string): (string, int)
{
# strip leading and trailing whitespace
WS: con " \t";
tz = S->drop(tz, WS);
for(n := len tz; n > 0; n--) {
if(S->in(tz[n-1], WS) == 0)
break;
}
if(n < len tz)
tz = tz[:n];
# if no timezone, default to GMT
if(tz == nil)
return ("GMT", 0);
# GMT aliases
case tz {
"GMT" or
"UT" or
"UTC" or
"Z" =>
return ("GMT", 0);
}
# [+-]hhmm (hours and minutes offset from GMT)
if(len tz == 5 && (tz[0] == '+' || tz[0] == '-')) {
h := int tz[1:3];
m := int tz[3:5];
if(h > 23 || m > 59)
return ("", 0);
tzoff := h*SEC2HOUR + m*SEC2MIN;
if(tz[0] == '-')
tzoff = -tzoff;
return ("GMT", tzoff);
}
# try continental US timezones
filename: string;
case tz {
"CST" or "CDT" =>
filename = "CST.CDT";
"EST" or "EDT" =>
filename = "EST.EDT";
"MST" or "MDT" =>
filename = "MST.MDT";
"PST" or "PDT" =>
filename = "PST.PDT";
* =>
; # default to local timezone
}
tzdata := readtimezone(filename);
if(tzdata.stname == tz)
return (tzdata.stname, tzdata.stdiff);
if(tzdata.dlname == tz)
return (tzdata.dlname, tzdata.dldiff);
return ("", 0);
}