BasicTimeImpl.mesa: low-level time facilities
Copyright Ó 1985, 1986, 1987, 1991 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) July 11, 1988 2:43:53 pm PDT
Hal Murray, June 3, 1986 3:31:31 pm PDT
Doug Wyatt, February 3, 1987 6:15:21 pm PST
Carl Hauser, October 27, 1988 1:56:38 pm PDT
JKF August 30, 1988 5:15:27 pm PDT
DIRECTORY
BasicTime USING [DayOfWeek, ExtendedGMT, minutesPerHour, MonthOfYear, nullGMT, Pulses, secondsPerMinute, Unpacked, UnpackedPeriod, unspecifiedTP, unspecifiedZone, Zone, ZoneAndDST],
BasicTimeBackdoor,
HostTime USING [ExtendedGMTFromHostTime, GetTime, GetZoneAndDST, TimeToMicroseconds];
BasicTimeImpl: CEDAR MONITOR
IMPORTS HostTime
EXPORTS BasicTime, BasicTimeBackdoor
= { OPEN BasicTime;
Global variables
knownTP: ZoneAndDST;
knownTPValid: BOOL ¬ FALSE;
Sampling this need not be locked because it can only change in ONE direction, and that will only happen after knownTP is set correctly.
Errors
OutOfRange: PUBLIC ERROR = CODE;
TimeNotKnown: PUBLIC ERROR = CODE;
TimeParametersNotKnown: PUBLIC ERROR = CODE;
Part I: Fine-grain timer
GetClockPulses: PUBLIC PROC RETURNS [CARD] = TRUSTED {
RETURN[HostTime.TimeToMicroseconds[HostTime.GetTime[]]]
};
PulsesToMicroseconds: PUBLIC PROC [p: Pulses] RETURNS [CARD] = {
RETURN [p];
};
PulsesToSeconds: PUBLIC PROC [p: Pulses] RETURNS [s: REAL] = {
RETURN [p*1.0e-6];
};
MicrosecondsToPulses: PUBLIC PROC [m: CARD] RETURNS [Pulses] = {
RETURN [m];
};
Part II: Packed times
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: INT = -- intersection of Alto expiry and 31 bits, minus "nullGMT" representation --
CARD[gmtOrigin] +
(IF altoExpiry > CARD[LAST[INT]] THEN LAST[INT] - gmtOrigin - 1
ELSE MIN[altoExpiry, CARD[LAST[INT]] - CARD[gmtOrigin] - 1])
- 1;
earliestGMT: PUBLIC GMT ¬ gmtOrigin + LONG[LAST[Zone]] * 60;
latestGMT: PUBLIC GMT ¬ gmtExpiry;
nullGMT: GMT ~ BasicTime.nullGMT;
Now: PUBLIC PROC RETURNS [gmt: GMT] = TRUSTED {
gmt ¬ HostTime.ExtendedGMTFromHostTime[HostTime.GetTime[]].gmt;
};
ExtendedNow: PUBLIC PROC RETURNS [egmt: ExtendedGMT] = TRUSTED {
egmt ¬ HostTime.ExtendedGMTFromHostTime[HostTime.GetTime[]];
};
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+CARD[g-gmtOrigin]];
};
ToNSTime: PUBLIC PROC [g: GMT] RETURNS [ CARD ] = {
Returns time according to the Xerox NS protocol time standard. No errors.
RETURN [alto1968+CARD[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 MonthOfYear OF CARDINAL =
[0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366];
startWeekday: DayOfWeek = Monday; -- January 1st 1968 was a Monday
UnpackZ: PUBLIC PROC [time: GMT, tp: ZoneAndDST] RETURNS [ unpacked: Unpacked ] = {
secs, mins, hrs: CARD;
IF time < earliestGMT OR time > latestGMT THEN ERROR OutOfRange[];
secs ¬ time - gmtOrigin;
mins ¬ secs / 60 -
(IF tp.zone = unspecifiedZone THEN 0 ELSE tp.zone);
hrs ¬ mins / 60;
unpacked.second ¬ secs MOD 60;
unpacked.minute ¬ mins MOD 60;
unpacked.zone ¬ (IF tp.zone = 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[DayOfWeek];
ENDLOOP;
FOR month: MonthOfYear IN 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 = unspecifiedZone
OR NOT CheckDateGE[unpacked, tp.beginDST, 2]
OR CheckDateGE[unpacked, tp.endDST, 1]
THEN EXIT;
hrs ¬ hrs+1;
unpacked.dst ¬ yes;
ENDLOOP;
};
Unpack: PUBLIC PROC [time: GMT] RETURNS [Unpacked] = {
RETURN [UnpackZ[time, GetZoneAndDST[]]];
};
Pack: PUBLIC PROC [unpacked: Unpacked] RETURNS [ time: GMT ] = {
Raises "OutOfRange" for times before 1968 or after 2036.
tp: 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 # unspecifiedZone
THEN unpacked.zone
ELSE IF tp.zone # 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[DayOfWeek];
ENDLOOP;
IF tp.zone = 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: 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 DayOfWeek[FIRST[DayOfWeek]..unpacked.weekday) DO
day ¬ day-1;
ENDLOOP; -- calculate day number of preceding Sunday
RETURN [day > dstDay-6] -- before/after the critical Sunday
}
};
secondsPerHour: NAT ~ secondsPerMinute*minutesPerHour;
UnpackPeriod: PUBLIC PROC [period: INT] RETURNS [UnpackedPeriod] ~ {
seconds: CARD ~ ABS[period];
minutes: CARD ~ seconds/secondsPerMinute;
hours: CARD ~ minutes/minutesPerHour;
RETURN [[
hours: hours,
minutes: minutes-hours*minutesPerHour,
seconds: seconds-minutes*secondsPerMinute,
negative: period<0
]];
};
PackPeriod: PUBLIC PROC [period: UnpackedPeriod] RETURNS [seconds: INT] ~ {
part: CARD ~ period.seconds+period.minutes*secondsPerMinute;
IF period.hours<0 THEN ERROR OutOfRange;
IF CARD[period.hours]>(CARD[INT.LAST]-part)/secondsPerHour THEN ERROR OutOfRange;
seconds ¬ part+period.hours*secondsPerHour;
IF period.negative THEN seconds ¬ -seconds;
};
Part IV: Finding and setting time and zone/dst information
GetTPFromHost: INTERNAL PROC RETURNS [ok: BOOL ¬ TRUE] = {
knownTP ¬ HostTime.GetZoneAndDST[];
knownTPValid ¬ TRUE;
};
SetZoneAndDST: PUBLIC ENTRY PROC [tp: ZoneAndDST] = {
ENABLE UNWIND => NULL;
knownTP ¬ tp;
knownTPValid ¬ TRUE;
};
GetZoneAndDST: PUBLIC ENTRY PROC RETURNS [tp: ZoneAndDST] = {
ENABLE UNWIND => NULL;
IF knownTPValid OR GetTPFromHost[].ok THEN RETURN [knownTP];
RETURN [ BasicTime.unspecifiedTP ];
};
}.