BasicTimeImpl.mesa: low-level time facilities
Copyright © 1985 by Xerox Corporation. All rights reserved.
Andrew Birrell November 30, 1983 9:31 am
Levin, December 16, 1983 3:44 pm
Russ Atkinson (RRA) June 6, 1985 3:54:51 am PDT
Hal Murray, June 3, 1986 3:31:31 pm PDT
DIRECTORY
Basics USING [LongDiv, LongDivMod, LongMult, LongNumber],
BasicTime USING [DayOfWeek, MonthOfYear, Pulses, Unpacked, unspecifiedZone, Zone, ZoneAndDST],
Endian USING [FWORD, CardFromF],
File USING [ SystemVolume, Volume],
GermSwap USING [switches],
PhysicalVolume USING [GetPhysical, Physical, PhysicalInfo, PhysicalRC, WriteTimeParameters],
ProcessorFace USING [GetGreenwichMeanTime, gmtEpoch, microsecondsPerHundredPulses, SetGreenwichMeanTime],
Pup USING [allHosts, allNets],
PupBuffer USING [Buffer],
PupSocket USING [AllocBuffer, Broadcast, CreateEphemeral, Destroy, FreeBuffer, Get, GetUniqueID, SetNoErrors, SetUserBytes, Socket],
PupWKS USING [misc];
BasicTimeImpl:
CEDAR
MONITOR
IMPORTS Basics, Endian, File, GermSwap, PhysicalVolume, ProcessorFace, PupSocket
EXPORTS BasicTime, PhysicalVolume = {
CARD: TYPE = LONG CARDINAL;
The monitor locks protects access to
ProcessorFace (GetGreenwichMeanTime & SetGreenwichMeanTime)
global frame (knownTP, knownTPValid)
disk parameters (PhysicalVolume.WriteTimeParameters & PhysicalVolume.PhysicalInfo)
PulsesToMicroseconds:
PUBLIC
PROC [p: BasicTime.Pulses]
RETURNS [
CARD ] = {
RETURN [MultThenDiv[p, ProcessorFace.microsecondsPerHundredPulses, 100]];
};
PulsesToSeconds:
PUBLIC
PROC [p: BasicTime.Pulses]
RETURNS [s:
REAL ] = {
Notice that we sample ProcessorFace.microsecondsPerHundredPulses every time through this routine. The (minor) extra cost is paid to deal with possibly changing clocks. Also, we are biased towards floating point multiplies as being faster than 32-bit multiplies.
RETURN [p*(ProcessorFace.microsecondsPerHundredPulses*1.0e-8)];
};
MicrosecondsToPulses:
PUBLIC
PROC [m:
CARD]
RETURNS [BasicTime.Pulses ] = {
RETURN [MultThenDiv[m, 100, ProcessorFace.microsecondsPerHundredPulses]];
};
MultThenDiv:
PROC [m1:
CARD, m2:
CARDINAL, dv:
CARDINAL]
RETURNS [result:
CARD] =
TRUSTED {
Copied from Pilot's SystemImpl.
OPEN mm1: LOOPHOLE[m1, num Basics.LongNumber];
t:
MACHINE
DEPENDENT
RECORD [
SELECT
OVERLAID *
FROM
separate => [low, mid, high: CARDINAL],
lower => [lowlong: CARD, junk: CARDINAL],
higher => [junk: CARDINAL, highlong: CARD],
ENDCASE];
t.lowlong ← Basics.LongMult[mm1.lowbits, m2];
IF mm1.highbits # 0
THEN {
t.highlong ← Basics.LongMult[mm1.highbits, m2] + t.mid;
IF t.high # 0
THEN {
OPEN q: LOOPHOLE[result, num Basics.LongNumber];
Have to do triple divide
IF t.high >= dv THEN t.high ← t.high MOD dv; -- overflow; lowbits will be right
[quotient: q.highbits, remainder: t.mid] ← Basics.LongDivMod[t.highlong, dv];
q.lowbits ← Basics.LongDiv[t.lowlong, dv];
RETURN
};
};
t.high is 0, so let mesa do the work...
RETURN [t.lowlong/LONG[dv]]
};
GMT: PUBLIC TYPE = INT;
gmtBaseYear: CARDINAL = 1968;
gmtOrigin: GMT = 0; -- representation for midnight, January 1st 1968 GMT
alto1968:
CARD =
-- our time origin in Alto/Pup format --
LONG[ (gmtBaseYear-1901) * 365 + (gmtBaseYear-1901)/4 ] * 24 * 60 * 60;
altoExpiry: CARD = LAST[CARD] - alto1968;
gmtExpiry:
GMT =
-- intersection of Alto expiry and 31 bits, minus "nullGMT" representation --
gmtOrigin + MIN[altoExpiry, LAST[INT] - gmtOrigin - 1] - 1;
earliestGMT: PUBLIC GMT ← gmtOrigin + LONG[LAST[BasicTime.Zone]] * 60;
latestGMT: PUBLIC GMT ← gmtExpiry;
Now:
PUBLIC
PROC
RETURNS [
GMT] =
TRUSTED {
prTime: CARD ← LockedGetTime[];
IF prTime = ProcessorFace.gmtEpoch
THEN {
means "unset clock"
ok: BOOL;
[ok, prTime] ← SetTimeAndTP[];
IF NOT ok THEN ERROR TimeNotKnown;
};
RETURN [prTime-alto1968+gmtOrigin ]
};
Period:
PUBLIC
PROC [from, to:
GMT]
RETURNS [
INT] = {
RETURN [to-from];
};
Update:
PUBLIC
PROC [base:
GMT, period:
INT]
RETURNS [
GMT] =
{
Returns the time "period" seconds later than "base" (earlier iff "period" is negative). Raises "OutOfRange" if the result would be before 1968 or after 2036.
IF period
IN [gmtOrigin-base .. gmtExpiry-base]
THEN RETURN [base+period]
ELSE ERROR OutOfRange;
};
ToPupTime:
PUBLIC
PROC [g:
GMT]
RETURNS [
CARD ] = {
Returns time according to the Alto/Pup time standard. No errors.
RETURN [ alto1968+(g-gmtOrigin) ];
};
ToNSTime:
PUBLIC
PROC [g:
GMT]
RETURNS [
CARD ] = {
Returns time according to the Xerox NS protocol time standard. No errors.
RETURN [alto1968+(g-gmtOrigin)];
};
FromPupTime:
PUBLIC
PROC [p:
CARD]
RETURNS [
GMT ] = {
Accepts time according to the Alto/Pup time standard. Raises "OutOfRange" for times earlier than 1968.
IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange;
};
FromNSTime:
PUBLIC
PROC [p:
CARD]
RETURNS [
GMT ] = {
Accepts time according to the Xerox NS protocol time standard. Raises "OutOfRange" for times beyond about 2036.
IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange;
};
Part III: Packing and unpacking
monthTable:
ARRAY BasicTime.MonthOfYear
OF
CARDINAL =
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
startWeekday: BasicTime.DayOfWeek = Monday;
-- January 1st 1968 was a Monday
Unpack:
PUBLIC
PROC [time:
GMT]
RETURNS [ unpacked: BasicTime.Unpacked ] = {
tp: BasicTime.ZoneAndDST = GetZoneAndDST[];
secs, mins, hrs: CARD;
IF time < earliestGMT OR time > latestGMT THEN ERROR OutOfRange[];
secs ← time - gmtOrigin;
mins ← secs / 60 -
(IF tp.zone = BasicTime.unspecifiedZone THEN 0 ELSE tp.zone);
hrs ← mins / 60;
unpacked.second ← secs MOD 60;
unpacked.minute ← mins MOD 60;
unpacked.zone ← (IF tp.zone = BasicTime.unspecifiedZone THEN 0 ELSE tp.zone);
unpacked.dst ← no;
DO
-- once if not dst, twice if dst
daysSinceBase: CARDINAL = hrs / 24;
daysInFourYears: CARDINAL = ( 3*365 + 366 );
fourYears: CARDINAL = daysSinceBase / daysInFourYears;
daysBeyondFourYears: CARDINAL = daysSinceBase MOD daysInFourYears;
pseudoDaysBeyond:
CARDINAL =
-- corrected as if every year had 366 days
daysBeyondFourYears + (MAX[31+29,daysBeyondFourYears]-(31+29))/365;
oddYears: [0..4) = pseudoDaysBeyond / 366;
dayOfYear: CARDINAL = (pseudoDaysBeyond MOD 366)+1; -- one's origin, 366-day year
unpacked.year ← gmtBaseYear + 4*fourYears + oddYears;
unpacked.weekday ← startWeekday;
THROUGH [0..daysSinceBase MOD 7)
DO unpacked.weekday ←
SUCC[unpacked.weekday];
IF unpacked.weekday = unspecified
THEN unpacked.weekday ← FIRST[BasicTime.DayOfWeek];
ENDLOOP;
FOR month: BasicTime.MonthOfYear IN BasicTime.MonthOfYear[January..December]
DO
IF dayOfYear <= monthTable[
SUCC[month]]
THEN {unpacked.month ← month; EXIT};
REPEAT FINISHED => ERROR
ENDLOOP;
unpacked.day ← dayOfYear-monthTable[unpacked.month];
unpacked.hour ← hrs MOD 24;
{
yearStart:
CARDINAL = fourYears * daysInFourYears +
(IF oddYears = 0 THEN 0 ELSE 366 + (oddYears-1)*365);
unpacked.daysThisYear ← daysSinceBase - yearStart;
unpacked.secondsThisYear ← secs - ((LONG[yearStart] * 24) * 60) * 60;
};
IF unpacked.dst = yes
OR tp.zone = BasicTime.unspecifiedZone
OR NOT CheckDateGE[unpacked, tp.beginDST, 2]
OR CheckDateGE[unpacked, tp.endDST, 1]
THEN EXIT;
hrs ← hrs+1;
unpacked.dst ← yes;
ENDLOOP;
};
Pack:
PUBLIC
PROC [unpacked: BasicTime.Unpacked]
RETURNS [ time:
GMT ] = {
Raises "OutOfRange" for times before 1968 or after 2036.
tp: BasicTime.ZoneAndDST = GetZoneAndDST[];
daysInFourYears: CARDINAL = ( 3*365 + 366 );
yearsSinceBase:
CARDINAL =
IF unpacked.year < gmtBaseYear
THEN ERROR OutOfRange
ELSE unpacked.year - gmtBaseYear;
oddYears: [0..4) = yearsSinceBase MOD 4;
day:
CARDINAL =
-- day number in year --
monthTable[unpacked.month] + unpacked.day - 1--adjust to zero origin--
- -- monthTable assumes all February's have 29 days, so correct for that --
(IF oddYears#0 AND unpacked.month NOT IN [January..February] THEN 1 ELSE 0);
daysSinceBase:
CARDINAL =
(yearsSinceBase/4) * daysInFourYears
+ oddYears * 365 + day
+ (IF oddYears # 0 THEN 1 ELSE 0) --leap day--;
zone: [-720-60..+721] ←
IF unpacked.zone # BasicTime.unspecifiedZone
THEN unpacked.zone
ELSE
IF tp.zone # BasicTime.unspecifiedZone
THEN tp.zone
ELSE ERROR TimeParametersNotKnown[];
SELECT unpacked.dst
FROM
unspecified => {
unpacked.weekday ← startWeekday;
THROUGH [0..daysSinceBase
MOD 7)
DO
unpacked.weekday ← SUCC[unpacked.weekday];
IF unpacked.weekday = unspecified
THEN
unpacked.weekday ← FIRST[BasicTime.DayOfWeek];
ENDLOOP;
IF tp.zone = BasicTime.unspecifiedZone THEN ERROR TimeParametersNotKnown[];
IF CheckDateGE[unpacked,tp.beginDST,2]
AND ~CheckDateGE[unpacked,tp.endDST,2]
THEN zone ← zone - 60;
};
yes => zone ← zone - 60;
no => NULL;
ENDCASE => ERROR;
RETURN [ gmtOrigin + ((LONG[daysSinceBase] * 24 + unpacked.hour) * 60 + unpacked.minute + zone) * 60 + unpacked.second ]
};
CheckDateGE:
PROC [unpacked: BasicTime.Unpacked, dstDay, dstHour:
CARDINAL]
RETURNS [
BOOL] = {
day: CARDINAL ← monthTable[unpacked.month] + unpacked.day;
SELECT
TRUE
FROM
day < dstDay-6 => RETURN [FALSE]; -- before the interesting week
day > dstDay => RETURN [TRUE]; -- after the interesting week
unpacked.weekday = Sunday => RETURN [unpacked.hour >= dstHour]; -- critical Sunday
ENDCASE => {
THROUGH BasicTime.DayOfWeek[
FIRST[BasicTime.DayOfWeek]..unpacked.weekday)
DO
day ← day-1;
ENDLOOP; -- calculate day number of preceding Sunday
RETURN [day > dstDay-6] -- before/after the critical Sunday
}
};
--VolumeFormat.--TimeParameters:
PUBLIC
TYPE =
MACHINE
DEPENDENT
RECORD[
direction(0:0..0): { west(0), east(1) }, -- California is west
zone(0:1..4): [0..12],
zoneMinutes(1:0..6): [0..60),
beginDST(0:5..15): [0..366], -- April 30 is 121
endDST(1:7..15): [0..366]
];
SetTime:
PUBLIC
PROC =
TRUSTED {
[] ← SetTimeAndTP[];
};
SetZoneAndDST:
PUBLIC
ENTRY
PROC [tp: BasicTime.ZoneAndDST] = {
ENABLE UNWIND => NULL;
clientTP: TimeParameters = [
direction: IF tp.zone > 0 THEN west ELSE east,
zone: ABS[tp.zone] / 60,
zoneMinutes: ABS[tp.zone] MOD 60,
beginDST: tp.beginDST,
endDST: tp.endDST];
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
IF sysPhys # NIL THEN [] ← PhysicalVolume.WriteTimeParameters[sysPhys, TRUE, clientTP];
ConvertPhysTP[clientTP];
};
GetZoneAndDST:
PUBLIC
PROC
RETURNS [tp: BasicTime.ZoneAndDST] = {
ENABLE UNWIND => NULL;
IF knownTPValid OR SetTimeAndTP[].ok OR TPFromDisk[] THEN RETURN [knownTP];
RETURN [ [zone: BasicTime.unspecifiedZone, beginDST: 366, endDST: 366] ];
};
SetTimeAndTP:
PROC
RETURNS [ok:
BOOL ←
FALSE, secs:
CARD ← ProcessorFace.gmtEpoch
--NS/Pilot--] = {
netTP: TimeParameters;
IF GermSwap.switches[c] THEN RETURN; -- => communication package isn't running
[ok, netTP, secs] ← TimeFromEthernet[];
IF NOT ok THEN [ok, netTP, secs] ← TimeFromEthernetOne[];
IF NOT ok THEN RETURN;
LockedSetTime[netTP, secs];
};
LockedGetTime:
ENTRY
PROC
RETURNS [
CARD] =
TRUSTED {
This is an ENTRY procedure to protect ProcessorFace.GetGreenwichMeanTime from interference by ProcessorFace.SetGreenwichMeanTime.
RETURN [ProcessorFace.GetGreenwichMeanTime[]];
};
LockedSetTime:
ENTRY
PROC [netTP: TimeParameters, secs:
CARD
--NS/Pilot--] = {
This is an ENTRY procedure to protect ConvertPhysTP and ProcessorFace.SetGreenwichMeanTime.
IF
NOT knownTPValid
THEN {
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
IF sysPhys # NIL THEN [] ← PhysicalVolume.WriteTimeParameters[sysPhys, TRUE, netTP];
ConvertPhysTP[netTP];
};
TRUSTED {ProcessorFace.SetGreenwichMeanTime[secs]};
};
TPFromDisk:
ENTRY
PROC
RETURNS [
BOOL] = {
sysPhys: PhysicalVolume.Physical = GetSysPhys[];
sysPhysStatus: PhysicalVolume.PhysicalRC;
physTP: TimeParameters;
physTPOK: BOOL;
IF sysPhys = NIL OR knownTPValid THEN RETURN [FALSE];
[rootStatus: sysPhysStatus, timeValid: physTPOK, time: physTP] ←
PhysicalVolume.PhysicalInfo[sysPhys];
IF sysPhysStatus # ok OR NOT physTPOK THEN RETURN [FALSE];
ConvertPhysTP[physTP];
RETURN [TRUE] -- even although we haven't set the clock value?
};
ConvertPhysTP:
INTERNAL
PROC [physTP: TimeParameters] = {
knownTP ← [
zone: physTP.zone*60 + physTP.zoneMinutes,
beginDST: physTP.beginDST,
endDST: physTP.endDST];
IF physTP.direction # west THEN knownTP.zone ← -knownTP.zone;
knownTPValid ← TRUE;
};
GetSysPhys:
PROC
RETURNS [PhysicalVolume.Physical] = {
sys: File.Volume = File.SystemVolume[];
RETURN [ IF sys = NIL THEN NIL ELSE PhysicalVolume.GetPhysical[sys] ];
};
TimeFromEthernet:
PROC
RETURNS [physTPOK:
BOOL, physTP: TimeParameters, time:
CARD] = {
physTPOK ← FALSE;
};
TimeFromEthernetOne:
PROC
RETURNS [physTPOK:
BOOL, physTP: TimeParameters, time:
CARD] =
TRUSTED {
id: Endian.FWORD = PupSocket.GetUniqueID[];
socket: PupSocket.Socket = PupSocket.CreateEphemeral[
remote: [net: Pup.allNets, host: Pup.allHosts, socket: PupWKS.misc],
getTimeout: 5000];
PupSocket.SetNoErrors[socket];
FOR i:
NAT
IN [0..10)
DO
b: PupBuffer.Buffer ← PupSocket.AllocBuffer[socket];
b.type ← altoTimeRequest;
b.id ← id;
PupSocket.SetUserBytes[b, 0];
PupSocket.Broadcast[socket, b];
DO
b ← PupSocket.Get[socket];
IF b = NIL THEN EXIT;
IF b.type = altoTimeReply
AND b.id = id
THEN {
physTP ← [
direction: IF b.time.direction = west THEN west ELSE east,
zone: b.time.zone,
zoneMinutes: b.time.zoneMinutes,
beginDST: b.time.beginDST,
endDST: b.time.endDST];
time ← Endian.CardFromF[b.time.time];
PupSocket.FreeBuffer[b];
GOTO Done; };
PupSocket.FreeBuffer[b];
ENDLOOP;
REPEAT
Done => physTPOK ← TRUE;
FINISHED => physTPOK ← FALSE
ENDLOOP;
PupSocket.Destroy[socket];
};
}.