<> <> <> <> <> <> <> 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; <> knownTP: ZoneAndDST; knownTPValid: BOOL ¬ FALSE; <> <<>> <> OutOfRange: PUBLIC ERROR = CODE; TimeNotKnown: PUBLIC ERROR = CODE; TimeParametersNotKnown: PUBLIC ERROR = CODE; <> 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]; }; <> 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] = { <> IF period IN [gmtOrigin-base .. gmtExpiry-base] THEN RETURN [base+period] ELSE ERROR OutOfRange; }; ToPupTime: PUBLIC PROC [g: GMT] RETURNS [ CARD ] = { <> RETURN [alto1968+CARD[g-gmtOrigin]]; }; ToNSTime: PUBLIC PROC [g: GMT] RETURNS [ CARD ] = { <> RETURN [alto1968+CARD[g-gmtOrigin]]; }; FromPupTime: PUBLIC PROC [p: CARD] RETURNS [ GMT ] = { <> IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange; }; FromNSTime: PUBLIC PROC [p: CARD] RETURNS [ GMT ] = { <> IF p >= alto1968 THEN RETURN [(p-alto1968)+gmtOrigin] ELSE ERROR OutOfRange; }; <> 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 ] = { <> 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; }; <<>> <> 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 ]; }; }.