-- TimeImpl.mesa (last edited by: McJones on: January 28, 1981 5:42 PM)

DIRECTORY
Inline USING [DIVMOD, LDIVMOD, LongNumber],
String USING [AppendChar, AppendString],
System USING [GetGreenwichMeanTime, GetLocalTimeParameters,
GreenwichMeanTime, gmtEpoch, LocalTimeParameters, SecondsSinceEpoch],
Time USING [defaultTime, Unpacked];

TimeImpl: PROGRAM
IMPORTS Inline, String, System EXPORTS Time =
BEGIN

StartWeekDay: CARDINAL = 0; -- Jan. 1, 1968 was a Monday
BaseYear: CARDINAL = 1968;
DaysInFourYears: CARDINAL = 4*365+1;

Unpacked: TYPE = Time.Unpacked;
UP: TYPE = POINTER TO Unpacked;
Number: TYPE = Inline.LongNumber;

DivideTime: PROC [num: Number, den: CARDINAL]
RETURNS [quotient: LONG CARDINAL, remainder: CARDINAL] =
BEGIN OPEN Inline;
q: Number;
t: CARDINAL;
[q.highbits, t] ← LDIVMOD[num.highbits,0,den];
[q.lowbits, remainder] ← LDIVMOD[num.lowbits,t,den];
RETURN[quotient: q.lc, remainder: remainder]
END;

Current: PUBLIC PROCEDURE RETURNS [System.GreenwichMeanTime] =
{RETURN[System.GetGreenwichMeanTime[]]};

TP: TYPE = RECORD [
beginDST, endDST: CARDINAL,
zone, zoneMinutes: INTEGER];

TimeParameters: PROCEDURE RETURNS [TP] =
BEGIN
ltp: System.LocalTimeParameters = System.GetLocalTimeParameters[];
p: TP ← [
beginDST: ltp.beginDST, endDST: ltp.endDST,
zone: ltp.zone, zoneMinutes: ltp.zoneMinutes];
IF ltp.direction # west THEN
{p.zone ← -p.zone; p.zoneMinutes ← -p.zoneMinutes};
RETURN[p]
END;

MonthTable: ARRAY [0..12] OF CARDINAL =
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];

Unpack: PUBLIC PROC [time: System.GreenwichMeanTime ← Time.defaultTime]
RETURNS [unpacked: Unpacked] =
BEGIN
seconds: LONG CARDINAL;
u: UP = @unpacked;
day4, day, yr4: CARDINAL;
month: CARDINAL ← 1;
t: Number;
parms: TP = TimeParameters[];

IF time = Time.defaultTime THEN time ← System.GetGreenwichMeanTime[];
seconds ← System.SecondsSinceEpoch[time];
[seconds, u.second] ← DivideTime[[lc[seconds]],60];
seconds ← seconds-parms.zoneMinutes; -- ignore underflow
[seconds, u.minute] ← DivideTime[[lc[seconds]],60];
u.zone ← parms.zone;
u.dst ← FALSE;
seconds ← seconds-parms.zone; -- ignore underflow
DO -- have to repeat if DST
[t.lc, u.hour] ← DivideTime[[lc[seconds]],24];
u.weekday ← (t.lowbits + StartWeekDay) MOD 7;
[yr4,day4] ← Inline.DIVMOD[t.lowbits,DaysInFourYears];
IF day4 >= (365+31+29) THEN
day4 ← (day4-31-29)/365 + day4;
[day4, day] ← Inline.DIVMOD[day4, 366];
u.year ← BaseYear + yr4*4 + day4;
WHILE day >= MonthTable[month] DO month ← month + 1 ENDLOOP;
u.month ← month ← month-1;
day ← day + 1;
u.day ← day - MonthTable[month];
IF u.dst OR ~CheckDateGE[u, parms.beginDST, 2]
OR CheckDateGE[u, parms.endDST, 1] THEN EXIT;
seconds ← seconds + 1;
u.dst ← TRUE;
ENDLOOP;
RETURN
END;

Invalid: PUBLIC ERROR = CODE;

Pack: PUBLIC PROCEDURE [
unpacked: Unpacked, computeDST: BOOLEAN ← TRUE]
RETURNS [time: System.GreenwichMeanTime] =
BEGIN
u: UP = @unpacked;
year, month, day, day1, hour, minute, second: CARDINAL;
zone: INTEGER;
dst: BOOLEAN;
yr3: [0..3];
t: Number;
tp: TP ← TimeParameters[];

[
year: year, month: month, day: day,
hour: hour, minute: minute, second: second,
zone: zone, dst: dst] ← u↑;
IF (year ← year-BaseYear) >= 136 OR
month >=12 OR day NOT IN[1..31] OR
hour >= 24 OR minute >= 60 OR second >= 60 THEN ERROR Invalid;
yr3 ← year MOD 4;
IF day > LOOPHOLE[MonthTable[month+1] - MonthTable[month], CARDINAL] OR
(month = 1 AND day = 29 AND yr3 # 0) THEN ERROR Invalid;

-- compute days this year in day1
day1 ← MonthTable[month] + day;
IF yr3 # 0 AND month > 1 THEN day1 ← day1 - 1;

t ← Number[num[
highbits: 0,
lowbits: (year/4)*DaysInFourYears + yr3*365 + day1 - 1]];
IF yr3 # 0 THEN t.lowbits ← t.lowbits + 1; -- first of four year cycle is leap
u.weekday ← (t.lowbits + StartWeekDay) MOD 7;
IF computeDST THEN
BEGIN OPEN tp;
IF CheckDateGE[u,beginDST,2]
AND ~CheckDateGE[u,endDST,2] THEN zone ← zone - 1
END
ELSE
BEGIN
tp.zone ← zone; tp.zoneMinutes ← 0;
IF dst THEN tp.zone ← tp.zone - 1;
END;
RETURN[[System.gmtEpoch +
((t.lc*24+hour+tp.zone)*60+minute+tp.zoneMinutes)*60+second]]
END;

Append: PUBLIC PROC [
s: STRING, unpacked: Unpacked, zone: BOOLEAN ← FALSE] =
BEGIN
u: UP = @unpacked;
p: CARDINAL ← s.length;
m: CARDINAL;
z: INTEGER;
KnownZones: TYPE = [4..10];
zones: PACKED ARRAY KnownZones OF CHARACTER ← [’A,’E,’C,’M,’P,’Y,’H];
w2d: PROC [v: CARDINAL] =
BEGIN
d1, d2: CARDINAL;
[d1, d2] ← Inline.DIVMOD[v,10];
IF d1 # 0 THEN s[p] ← ’0 + d1;
s[p+1] ← ’0 + d2;
p ← p + 3;
END;

String.AppendString[s, " 0-xxx-00 0:00:00"L];
w2d[u.day];
m ← u.month*3;
THROUGH [0..2] DO
s[p] ← ("JanFebMarAprMayJunJulAugSepOctNovDec"L)[m];
p ← p + 1; m ← m + 1;
ENDLOOP;
p ← p + 1;
w2d[u.year MOD 100];
w2d[u.hour];
w2d[u.minute];
w2d[u.second];
IF zone AND (z←unpacked.zone) IN KnownZones THEN
BEGIN OPEN String;
c: CHARACTER ← IF unpacked.dst THEN ’D ELSE ’S;
AppendChar[s,’ ];
AppendChar[s,zones[z]];
AppendChar[s,c];
AppendChar[s,’T];
END;
RETURN
END;

AppendCurrent: PUBLIC PROC [s: STRING, zone: BOOLEAN ← FALSE] =
{Append[s, Unpack[], zone]};

CheckDateGE: PROC [u: UP, dstDay, dstHour: INTEGER] RETURNS [BOOLEAN] =
BEGIN
weekday: INTEGER ← u.weekday;
day: INTEGER = MonthTable[u.month] + u.day;
RETURN[
IF day < dstDay-6 THEN FALSE
ELSE IF day > dstDay THEN TRUE
ELSE IF weekday = 6 THEN u.hour >= dstHour
ELSE day-weekday > dstDay-6]
END;

END.

LOG

July 15, 1979 3:33 PM
JohnssonCreate file
February 9, 1980 1:44 PM
McJonesUse System.GreenwichMeanTime
March 1, 1980 3:50 PM
JohnssonFix leapyear bug introduced in change of epoch
April 17, 1980 11:14 AM
McJonesChange default time for Unpack to gmtEpoch
September 30, 1980 9:36 AM
JohnssonVarious bugs
January 28, 1981 4:40 PM
McJonesUse System.GetLocalTimeParameters