DIRECTORY Convert USING [IntFromRope, ValueToRope], IO USING [RIS, Backup, GetIndex, GetRope, SyntaxError, EndOfStream, GetSequence, EveryThing, WhiteSpace, BreakProc, GetToken, STREAM], Rope USING [ROPE, Equal, Concat, Find, Substr, Cat, Length], SafeStorage USING [NarrowFault], Tempus , -- everything Time USING [Invalid, Unpack, Unpacked, Pack] ; TempusImpl: CEDAR PROGRAM IMPORTS Convert, IO, Rope, SafeStorage, Time EXPORTS Tempus = BEGIN OPEN Tempus; ROPE: TYPE = Rope.ROPE; Error: PUBLIC ERROR [ec: ErrorCode] = CODE; Unintelligible: PUBLIC ERROR [rope: ROPE, vicinity: INT _ -1, ec: ErrorCode] = CODE; Unpack: PUBLIC PROCEDURE [time: Packed _ defaultTime] -- default means now -- RETURNS [unpacked: Unpacked] = TRUSTED { t: Time.Unpacked _ Time.Unpack[time]; RETURN[[ year: t.year, -- base year is 1968 month: LOOPHOLE[t.month, MonthOfYear], day: t.day, -- first day of month = 1. hour: t.hour, minute: t.minute, second: t.second, zone: t.zone*60, dst: t.dst, weekday: LOOPHOLE[t.weekday, DayOfWeek], secondsThisYear: 0, -- Roy: how do I do this? daysThisYear: 0 -- Roy: how do I do this? ]]; }; Pack: PUBLIC PROCEDURE [unpacked: Unpacked] RETURNS [time: Packed] = TRUSTED { IF unpacked.month = unspecified OR unpacked.weekday = unspecified THEN Error[invalid]; RETURN[Time.Pack[unpacked: [ year: unpacked.year, month: LOOPHOLE[unpacked.month], day: unpacked.day, hour: unpacked.hour, minute: unpacked.minute, second: unpacked.second, weekday: LOOPHOLE[unpacked.weekday], zone: unpacked.zone/60, dst: unpacked.dst] ! Time.Invalid => Error[invalid]]]; }; SmartPack: PUBLIC PROCEDURE [ year: [0..2050] _ 0, month: MonthOfYear _ unspecified, day: [0..daysPerMonth] _ 0, hour: [0..hoursPerDay] _ hoursPerDay, minute: [0..minutesPerHour] _ minutesPerHour, second: [0..secondsPerMinute] _ secondsPerMinute, zone: INT _ LAST[INT], secondsThisYear: INT _ LAST[INT], daysThisYear: [0..daysPerYear] _ daysPerYear, weekday: DayOfWeek _ unspecified, baseTime: Packed _ defaultTime ] RETURNS [time: Packed, precision: Precision] = { now, desired: Unpacked _ Unpack[baseTime]; Increment: PROC = { -- increments the appropriate quantity, checks for rollover IF year # 0 THEN NULL -- if user says April 27, 1982. allow this. ELSE IF weekday # unspecified THEN { IF month # unspecified OR day # 0 THEN Error[overConstrained]; -- e.g. user said Friday, April 7, 4PM and it is now 5PM. April 7 of following year would not be a Friday. THROUGH [0..7) DO IncrementDay[]; ENDLOOP; } ELSE IF month # unspecified THEN IncrementYear[] ELSE IF day # 0 THEN IncrementMonth[] ELSE IF hour # hoursPerDay THEN IncrementDay[] ELSE IF minute # minutesPerHour THEN IncrementHour[] ELSE ERROR; }; IncrementYear: PROC = { desired.year _ desired.year + 1; }; IncrementMonth: PROC = { desired.month _ SUCC[desired.month]; IF desired.month > December THEN { IncrementYear[]; desired.month _ January; }; }; IncrementDay: PROC = { IF desired.day < (SELECT desired.month FROM September, April, June, November => 30, February => IF desired.year MOD 4 = 0 THEN 29 ELSE 28, ENDCASE => 31) THEN desired.day _ desired.day + 1 ELSE { IncrementMonth[]; desired.day _ 1; }; }; IncrementHour: PROC = { desired.hour _ desired.hour + 1; IF desired.hour = hoursPerDay THEN { IncrementDay[]; desired.hour _ 0; }; }; IncrementMinute: PROC = { desired.minute _ desired.minute + 1; IF desired.minute = minutesPerHour THEN { IncrementHour[]; desired.minute _ 0; }; }; precision _ unspecified; IF year # 0 THEN { desired.year _ year; precision _ years; }; IF month # unspecified THEN { IF precision = unspecified AND month < desired.month THEN Increment[]; -- e.g. user said March and it is now June. Note that if user said April, we may still have to increment the year later if he specified a day before the current day. desired.month _ month; precision _ months; } ELSE IF precision # unspecified THEN desired.month _ January; -- e.g. user just said 1984 IF day # 0 THEN { SELECT precision FROM years => Error[tooVague]; months => IF desired.month = now.month AND (day < desired.day) THEN Increment[]; -- e.g. user said April 3 and it is now April 10.; unspecified => IF day < desired.day THEN Increment[]; -- e.g. user said the 3rd and it is now the 10th. ENDCASE => ERROR; desired.day _ day; precision _ days; } ELSE IF precision # unspecified THEN { IF desired.month = now.month THEN Increment[]; -- user said April (no date) and it is now already April. desired.day _ 1; -- e.g. user just said June }; IF weekday # unspecified THEN { ComputeDay: PROC [currentWeekDay: DayOfWeek] = { t: Unpacked _ Unpack[Pack[desired]]; n: INTEGER; SELECT TRUE FROM weekday > currentWeekDay => n _ LOOPHOLE[weekday, INTEGER] - LOOPHOLE[currentWeekDay, INTEGER]; weekday = currentWeekDay => n _ 0; -- this day is acceptable weekday < currentWeekDay => n _ 7 - (LOOPHOLE[currentWeekDay, INTEGER] - LOOPHOLE[weekday, INTEGER]); ENDCASE => ERROR; THROUGH [0..n) DO IncrementDay[]; -- this is not a case of incrementing because of rollover, but simply wanting to add 1 to the day ENDLOOP; }; SELECT precision FROM years => Error[tooVague]; months => { -- means such day in corresponding month t: Unpacked _ Unpack[Pack[desired]]; ComputeDay[t.weekday]; }; unspecified => { ComputeDay[desired.weekday]; }; days => { time _ Pack[desired]; desired _ Unpack[time]; IF desired.weekday # weekday THEN Error[overConstrained]; -- user said Friday, April 3, but April 3 is not Friday. }; ENDCASE => ERROR; precision _ days; }; IF hour # hoursPerDay THEN { SELECT precision FROM years, months => Error[tooVague]; days => IF desired.day = now.day AND hour < desired.hour AND desired.month = now.month THEN Increment[]; unspecified => IF hour < desired.hour THEN Increment[]; -- will increment day ENDCASE => ERROR; desired.hour _ hour; precision _ hours; } ELSE IF precision # unspecified THEN { IF desired.day = now.day AND desired.month = now.month THEN Increment[]; -- user said April 27 (no hour) and it is now already April 27, i.e. April 27, 12AM has passed. desired.hour _ 0; }; IF minute # minutesPerHour THEN { SELECT precision FROM years, months, days => Error[tooVague]; hours => IF desired.hour = now.hour AND minute < desired.minute AND desired.day = now.day AND desired.month = now.month THEN Increment[]; unspecified => IF minute < desired.minute THEN Increment[]; ENDCASE => ERROR; desired.minute _ minute; precision _ minutes; } ELSE IF precision # unspecified THEN { IF desired.hour = now.hour AND desired.day = now.day AND desired.month = now.month THEN Increment[]; -- user said April 27, 11AM and it is now April 27, 11:30. desired.minute _ 0; }; IF second # secondsPerMinute THEN { SELECT precision FROM years, months, days, hours => Error[tooVague]; minutes => IF desired.minute = now.minute AND second < desired.second AND desired.hour = now.hour AND desired.day = now.day AND desired.month = now.month THEN Increment[]; unspecified => IF second < desired.second THEN Increment[]; ENDCASE => ERROR; desired.second _ second; precision _ seconds; } ELSE IF precision # unspecified THEN { IF desired.minute = now.minute AND desired.hour = now.hour AND desired.day = now.day AND desired.month = now.month THEN Increment[]; desired.second _ 0; }; IF secondsThisYear # LAST[INT] THEN Error[notImplemented]; IF daysThisYear # daysPerYear THEN Error[notImplemented]; time _ Pack[desired]; }; Adjust: PUBLIC PROCEDURE [ years: INT _ LAST[INT], months: INT _ LAST[INT], days: INT _ LAST[INT], hours: INT _ LAST[INT], minutes: INT _ LAST[INT], seconds: INT _ LAST[INT], baseTime: Packed _ defaultTime, precisionOfResult: Precision _ unspecified ] RETURNS [time: Packed, precision: Precision] = { unpacked: Unpacked _ Unpack[baseTime]; IncrementYears: PROC[by: INT _ 1] = { unpacked.year _ unpacked.year + by; unpacked.day _ MIN[unpacked.day, DaysInMonth[]]; -- one year from February 29 is February 28 }; IncrementMonths: PROC [by: INT _ 1] = { IF by >= 0 THEN FOR i: INT IN [0..by) DO IF unpacked.month = December THEN {IncrementYears[]; unpacked.month _ January} ELSE unpacked.month _ SUCC[unpacked.month]; REPEAT FINISHED => unpacked.day _ MIN[unpacked.day, DaysInMonth[]]; -- if it is now March 31 and client says increment one month, that is April 30. If it is now January 29, 30 or 31 and client says increment one month, goes to February 28/29. ENDLOOP ELSE FOR i: INT IN [0..-by) DO IF unpacked.month = January THEN {IncrementYears[-1]; unpacked.month _ December} ELSE unpacked.month _ PRED[unpacked.month]; ENDLOOP; }; DaysInMonth: PROC RETURNS[[0..daysPerMonth]] = { RETURN[SELECT unpacked.month FROM September, April, June, November => 30, February => IF unpacked.year MOD 4 = 0 THEN 29 ELSE 28, ENDCASE => 31]; }; IncrementDays: PROC [by: INT _ 1] = { daysInMonth: [0..daysPerMonth]; daysInMonth _ DaysInMonth[]; IF by >= 0 THEN FOR i: INT IN [0..by) DO IF unpacked.day = daysInMonth THEN { IncrementMonths[]; daysInMonth _ DaysInMonth[]; unpacked.day _ 1; } ELSE unpacked.day _ unpacked.day + 1; ENDLOOP ELSE FOR i: INT IN [0..-by) DO IF unpacked.day = 1 THEN { IncrementMonths[-1]; daysInMonth _ DaysInMonth[]; unpacked.day _ daysInMonth; } ELSE unpacked.day _ unpacked.day - 1; ENDLOOP; }; IncrementHours: PROC [by: INT _ 1] = { IF by >= 0 THEN FOR i: INT IN [0..by) DO IF unpacked.hour = 23 THEN {IncrementDays[]; unpacked.hour _ 0} ELSE unpacked.hour _ unpacked.hour + 1; ENDLOOP ELSE FOR i: INT IN [0..-by) DO IF unpacked.hour = 0 THEN {IncrementDays[-1]; unpacked.hour _ 23} ELSE unpacked.hour _ unpacked.hour - 1; ENDLOOP; }; IncrementMinutes: PROC [by: INT _ 1] = { IF by >= 0 THEN FOR i: INT IN [0..by) DO IF unpacked.minute = 59 THEN {IncrementHours[]; unpacked.minute _ 0} ELSE unpacked.minute _ unpacked.minute + 1; ENDLOOP ELSE FOR i: INT IN [0..-by) DO IF unpacked.minute = 0 THEN {IncrementHours[-1]; unpacked.minute _ 59} ELSE unpacked.minute _ unpacked.minute - 1; ENDLOOP; }; IncrementSeconds: PROC [by: INT _ 1] = { IF by >= 0 THEN FOR i: INT IN [0..by) DO IF unpacked.second = 59 THEN {IncrementMinutes[]; unpacked.second _ 0} ELSE unpacked.second _ unpacked.second + 1; ENDLOOP ELSE FOR i: INT IN [0..-by) DO IF unpacked.second = 0 THEN {IncrementMinutes[-1]; unpacked.second _ 59} ELSE unpacked.second _ unpacked.second - 1; ENDLOOP; }; IF years # LAST[INT] THEN {IncrementYears[years]; precision _ years}; IF months # LAST[INT] THEN {IncrementMonths[months]; precision _ months}; IF days # LAST[INT] THEN {IncrementDays[days]; precision _ days}; IF hours # LAST[INT] THEN {-- IncrementHours[hours]; -- precision _ hours}; IF minutes # LAST[INT] THEN {-- IncrementMinutes[minutes]; -- precision _ minutes}; IF seconds # LAST[INT] THEN {-- IncrementSeconds[seconds]; -- precision _ seconds}; IF precisionOfResult = unspecified THEN precisionOfResult _ precision; IF precisionOfResult < seconds THEN unpacked.second _ 0; IF precisionOfResult < minutes THEN unpacked.minute _ 0; IF precisionOfResult < hours THEN unpacked.hour _ 0; IF precisionOfResult < days THEN unpacked.day _ 1; IF precisionOfResult < months THEN unpacked.month _ January; time _ Pack[unpacked]; IF hours # LAST[INT] THEN time _ [time + (hours * minutesPerHour * secondsPerMinute)]; IF minutes # LAST[INT] THEN time _ [time + minutes * secondsPerMinute]; IF seconds # LAST[INT] THEN time _ [time + seconds]; RETURN[time, precisionOfResult]; }; Parse: PUBLIC PROCEDURE [rope: Rope.ROPE, baseTime: Packed _ defaultTime, search: BOOL _ TRUE] RETURNS [time: Packed, precision: Precision, start, length: NAT] = { break: IO.BreakProc = { RETURN[SELECT char FROM ',, '., '?, '(, ') => sepr, -- I have seen dates written as Tuesday (April 12) ':, '-, '/, '\" => break, ENDCASE => IO.WhiteSpace[char]]; }; IsAnHour: PROC [i: INT, mustBe: BOOL _ FALSE] RETURNS[BOOL _ TRUE] = { IF i < 0 OR hour # hoursPerDay THEN GOTO No ELSE IF i IN [0..hoursPerDay) THEN hour _ i ELSE IF i < 100 THEN GOTO No ELSE IF i < 2400 THEN { -- i is between 100 and 2400 IF (i MOD 100) < 60 THEN {hour _ i/100; minute _ i MOD 100} ELSE GOTO No; } ELSE GOTO No; isATime _ TRUE; RETURN[TRUE]; EXITS No => IF mustBe THEN Error[invalid] ELSE RETURN[FALSE]; }; IsAMinute: PROC [i: INT, mustBe: BOOL _ FALSE] RETURNS[BOOL _ TRUE] = { IF minute = minutesPerHour AND hour # hoursPerDay AND i IN [0..minutesPerHour) THEN minute _ i ELSE IF mustBe THEN Error[invalid] ELSE RETURN[FALSE]; isATime _ TRUE; }; IsASecond: PROC [i: INT, mustBe: BOOL _ FALSE] RETURNS[BOOL _ TRUE] = { IF second = secondsPerMinute AND minute # minutesPerHour AND hour # hoursPerDay AND i IN [0..secondsPerMinute) THEN second _ i ELSE IF mustBe THEN Error[invalid] ELSE RETURN[FALSE]; isATime _ TRUE; }; IsAMonth: PROC [i: INT, mustBe: BOOL _ FALSE] RETURNS[BOOL _ TRUE] = { IF month = unspecified AND i IN [1..monthsPerYear] THEN month _ LOOPHOLE[INTEGER[i - 1], MonthOfYear] ELSE IF mustBe THEN Error[invalid] ELSE RETURN[FALSE]; isADate _ TRUE; }; IsADayOfMonth: PROC [i: INT, mustBe: BOOL _ FALSE] RETURNS[yes: BOOL _ TRUE] = { IF day = 0 AND i IN [1..daysPerMonth] THEN day _ i ELSE IF mustBe THEN Error[invalid] ELSE RETURN[FALSE]; isADate _ TRUE; }; IsAYear: PROC [i: INT, mustBe: BOOL _ FALSE] RETURNS[yes: BOOL _ TRUE] = { IF year = 0 AND i IN [1900..2050] THEN year _ i ELSE IF year = 0 AND i IN [minutesPerHour..99] THEN year _ 1900 + i -- to handle things like 25 apr 83 ELSE IF mustBe THEN Error[invalid] ELSE RETURN[FALSE]; isADate _ TRUE; }; AMPMCheck: PROC = { IF (am OR pm) AND hour = hoursPerDay THEN Error[invalid]; -- if hours not specified when this is called, means somebody said am or pm without hour. IF pm THEN {IF hour > 12 THEN Error[invalid] ELSE IF hour < 12 THEN hour _ hour + 12} ELSE IF am THEN {IF hour > 12 THEN Error[invalid] ELSE IF hour = 12 THEN hour _ 0} ELSE IF hour <= 7 THEN hour _ hour + 12 -- if no am or pm specified, treat 2 as 2PM }; stream: IO.STREAM = IO.RIS[rope]; token, prevToken, nextToken: ROPE; am, pm, isAnInterval, isADate, isATime: BOOL _ FALSE; year: [0..2050] _ 0; month: MonthOfYear _ unspecified; day: [0..daysPerMonth] _ 0; hour: [0..hoursPerDay] _ hoursPerDay; minute: [0..minutesPerHour] _ minutesPerHour; second: [0..secondsPerMinute] _ secondsPerMinute; zone: INT _ LAST[INT]; weekday: DayOfWeek _ unspecified; years, months, days, hours, minutes, seconds: INT _ LAST[INT]; now: Unpacked _ Unpack[baseTime]; i: INT; before: BOOL _ FALSE; precisionOfResult: Precision _ unspecified; prevNum: INT _ -1; -- -1 means previous token was not a number last: INT _ -1; why: ErrorCode _ invalid; start _ 0; BEGIN -- establish scope to transform Tempus.Error into Tempus.Uninteligible ENABLE Error => Unintelligible[rope: rope, vicinity: IO.GetIndex[stream], ec: ec]; DO prevToken _ token; IF nextToken # NIL THEN {token _ nextToken; nextToken _ NIL} ELSE IF (token _ IO.GetToken[stream, break]) = NIL THEN EXIT; { SELECT TRUE FROM RopeEqual[token, "\""] => { stream.Backup['\"]; token _ prevToken; nextToken _ stream.GetRope[ ! IO.SyntaxError, IO.EndOfStream => EXIT]; LOOP; }; RopeEqual[token, "years"], RopeEqual[token, "year"] => { IF prevNum # -1 THEN years _ prevNum ELSE IF Rope.Equal[prevToken, "a"] THEN years _ 1 ELSE Error[invalid]; precisionOfResult _ years; }; RopeEqual[token, "months"], RopeEqual[token, "month"] => { IF prevNum # -1 THEN months _ prevNum ELSE IF Rope.Equal[prevToken, "a"] THEN months _ 1 ELSE Error[invalid]; precisionOfResult _ months; }; RopeEqual[token, "weeks"], RopeEqual[token, "week"] => { IF prevNum # -1 THEN days _ prevNum * 7 ELSE IF Rope.Equal[prevToken, "a"] THEN days _ 7 ELSE Error[invalid]; precisionOfResult _ days; }; RopeEqual[token, "days"], RopeEqual[token, "day"] => { IF prevNum # -1 THEN days _ prevNum ELSE IF Rope.Equal[prevToken, "a"] THEN days _ 1 ELSE Error[invalid]; precisionOfResult _ days; }; RopeEqual[token, "hours"], RopeEqual[token, "hour"] => { IF prevNum # -1 THEN hours _ prevNum ELSE IF Rope.Equal[prevToken, "an"] THEN hours _ 1 ELSE Error[invalid]; precisionOfResult _ hours; }; RopeEqual[token, "minutes"], RopeEqual[token, "minute"] => { IF prevNum # -1 THEN minutes _ prevNum ELSE IF Rope.Equal[prevToken, "a"] THEN minutes _ 1 ELSE Error[invalid]; precisionOfResult _ minutes; }; RopeEqual[token, "seconds"] => { IF prevNum # -1 THEN seconds _ prevNum ELSE IF Rope.Equal[prevToken, "a"] THEN seconds _ 1 ELSE Error[invalid]; precisionOfResult _ seconds; }; ENDCASE => IF prevNum = -1 THEN GOTO NextBlock ELSE IF IsAnHour[prevNum] OR IsAMinute[prevNum] OR IsASecond[prevNum] THEN {prevNum _ -1; GOTO NextBlock} ELSE IF isAnInterval OR isADate OR isATime THEN Error[invalid] ELSE {prevNum _ -1; GOTO NextBlock}; -- havent seen any part of a time or date yet. This is just a number that happens to be in the text, e.g. a phone number prevNum _ -1; isAnInterval _ TRUE; length _ stream.GetIndex[] - start; LOOP; EXITS NextBlock => NULL; -- drop through to next }; { w: DayOfWeek; m: MonthOfYear; IF (w _ IsDayOfWeek[token]) # unspecified THEN { weekday _ w; isADate _ TRUE; } ELSE IF (m _ IsMonthOfYear[token]) # unspecified THEN { month _ m; isADate _ TRUE; } ELSE SELECT TRUE FROM RopeEqual[token, "Tomorrow"] => { IF now.weekday = Sunday THEN weekday _ Monday ELSE weekday _ SUCC[now.weekday]; isADate _ TRUE; }; RopeEqual[token, "Yesterday"] => { t: Unpacked _ Unpack[Adjust[days: -1, baseTime: baseTime].time]; year _ t.year; month _ t.month; day _ t.day; isADate _ TRUE; }; RopeEqual[token, "today"] => { year _ now.year; -- so 1:30 today doesn't parse as 1:30 tomorrow if it is now 3pm month _ now.month; day _ now.day; isADate _ TRUE; }; RopeEqual[token, "now"] => { year _ now.year; month _ now.month; day _ now.day; isADate _ TRUE; hour _ now.hour; minute _ now.minute; second _ now.second; isATime _ TRUE; }; RopeEqual[token, "am"], RopeEqual[token, "a.m."] => {am _ TRUE; isATime _ TRUE}; RopeEqual[token, "pm"], RopeEqual[token, "p.m"] => {pm _ TRUE; isATime _ TRUE}; (i _ Rope.Find[s1: token, s2: "am", case: FALSE]) # -1 => { IF (i + 2) = Rope.Length[token] THEN { am _ TRUE; nextToken _ "am"; -- reason for not simply setting am to TRUE and skipping this token. is that for something like 1:15PM June ... the fact that the pM wasn't seen would confuse this with something like 15 June ... On the other hand, if you don't set am to true, then something like Monday, August, 3PM would cause the 3 to get interpreted as the day. token _ Rope.Substr[base: token, len: i]; }; GOTO NextBlock; }; (i _ Rope.Find[s1: token, s2: "pm", case: FALSE]) # -1 => { IF (i + 2) = Rope.Length[token] THEN { pm _ TRUE; nextToken _ "pm"; token _ Rope.Substr[base: token, len: i]; }; GOTO NextBlock; }; RopeEqual[token, "noon"] => { hour _ 12; pm _ TRUE; isATime _ TRUE; }; ENDCASE => GOTO NextBlock; length _ stream.GetIndex[] - start; LOOP; EXITS NextBlock => NULL; -- drop through to next }; { SELECT TRUE FROM RopeEqual[token, "one"] => prevNum _ 1; RopeEqual[token, "two"] => prevNum _ 2; RopeEqual[token, "ten"] => prevNum _ 10; RopeEqual[token, "fifteen"] => prevNum _ 15; ENDCASE => GOTO NextBlock; length _ stream.GetIndex[] - start; LOOP; EXITS NextBlock => NULL; -- drop through to next }; SELECT TRUE FROM RopeEqual[token, "from"], RopeEqual[token, "after"], RopeEqual[token, "before"] => IF isAnInterval OR isADate OR isATime THEN { -- e.g. two weeks from Thursday, Thursday after May 1 precision: Precision; [baseTime, precision] _ Parse[rope: IO.GetSequence[stream, IO.EveryThing], baseTime: baseTime]; now _ Unpack[baseTime]; IF precisionOfResult = unspecified OR precision > precisionOfResult THEN precisionOfResult _ precision; IF hour # hoursPerDay THEN hours _ hour; -- e.g. 11:45 a week from Thursday. baseTime is now Thursday. IF minute # minutesPerHour THEN minutes _ minute; IF second # secondsPerMinute THEN seconds _ second; IF RopeEqual[token, "before"] THEN before _ TRUE; EXIT; }; RopeEqual[token, "in"], RopeEqual[token, "at"] => { length _ stream.GetIndex[] - start; LOOP; -- if say at 3PM, and search = FALSE, it should still find the time. }; RopeEqual[token, ":"], RopeEqual[token, "/"], RopeEqual[token, "-"] => { length _ stream.GetIndex[] - start; LOOP; }; ENDCASE; { i _ -1; i _ Convert.IntFromRope[token ! SafeStorage.NarrowFault => CONTINUE]; IF i = -1 THEN GOTO ExitCheck; IF isAnInterval THEN prevNum _ i -- the next object should/must be "months", "hours", "minutes", etc. ELSE IF hour = hoursPerDay AND (am OR pm) THEN [] _ IsAnHour[i, TRUE] -- e.g. 4PM, 430PM ELSE IF month # unspecified AND day = 0 THEN [] _ IsADayOfMonth[i, TRUE] -- e.g. April 27 ELSE IF month # unspecified AND day # 0 THEN { -- month and day specified IF year # 0 THEN { -- year specified, could be hour, minute, or second IF hour = hoursPerDay THEN [] _ IsAnHour[i, TRUE] -- e.g. April 27, 1983, 430. Must be an hour ELSE IF minute = minutesPerHour THEN [] _ IsAMinute[i, TRUE] -- e.g. April 27, 1983 4 30 ELSE [] _ IsASecond[i, TRUE]; -- e.g. April 27, 1983 4 30 30 } ELSE IF hour # hoursPerDay THEN {IF IsAYear[i] OR IsAMinute[i, TRUE] THEN NULL} -- year not specified, hour was specified could be year or minute, e.g. 4PM April 27, 1983, or April 27, 4 30. ELSE IF IsAYear[i] OR IsAnHour[i] OR IsAMinute[i] OR IsASecond[i, TRUE] THEN NULL; -- neither year nor hour specified, could be hour, minute, second, or year e.g. April 27, 430, April 27, 1983, or April 27, 430. } ELSE { -- saw a number with not enough context to decide whether day, month, or hour, look at next thing for a clue. IF nextToken = NIL THEN nextToken _ IO.GetToken[stream, break]; SELECT TRUE FROM RopeEqual[nextToken, ":"] => { [] _ IsAnHour[i, TRUE]; -- e.g. april 25, 3:40 nextToken _ NIL; }; RopeEqual[nextToken, "/"] => { [] _ IsAMonth[i, TRUE]; -- e.g. 4/27/83 nextToken _ NIL; }; RopeEqual[nextToken, "-"] => { -- e.g. 25-apr-83 nextToken _ IO.GetToken[stream, break]; IF IsMonthOfYear[nextToken] # unspecified THEN [] _ IsADayOfMonth[i, TRUE] ELSE GOTO ExitCheck; -- - not part of time, but don't raise error. might be a phone number. }; nextToken = NIL => { IF IsAnHour[i] OR IsAMinute[i] OR IsASecond[i] THEN NULL ELSE Error[tooVague]; }; ENDCASE => IF IsMonthOfYear[nextToken] # unspecified THEN [] _ IsADayOfMonth[i, TRUE] -- e.g. 4 apr 83 ELSE prevNum _ i; -- e.g. 2 weeks }; length _ stream.GetIndex[] - start; LOOP; EXITS ExitCheck => -- the current token was not recognized as part of a time/date IF isAnInterval THEN EXIT ELSE IF isADate OR isATime THEN { IF isADate AND isATime THEN EXIT ELSE LOOP; -- so date and time can be separated by noise words, e.g. Thursday morning at 10:30, 10:30 on the morning of July 13. } ELSE IF NOT search THEN Unintelligible[rope, start, nothingSpecified] ELSE {start _ stream.GetIndex[]; LOOP}; }; ENDLOOP; AMPMCheck[]; IF NOT isATime AND NOT isADate AND NOT isAnInterval THEN Error[nothingSpecified] -- exited because ran out of characters in rope, or because search = FALSE ELSE IF isAnInterval THEN [time, precision] _ Adjust[ years: IF years # LAST[INT] AND before THEN -years ELSE years, months: IF months # LAST[INT] AND before THEN -months ELSE months, days: IF days # LAST[INT] AND before THEN -days ELSE days, hours: IF hours # LAST[INT] AND before THEN -hours ELSE hours, minutes: IF minutes # LAST[INT] AND before THEN -minutes ELSE minutes, seconds: IF seconds # LAST[INT] AND before THEN -seconds ELSE seconds, baseTime: baseTime, precisionOfResult: IF precisionOfResult = unspecified THEN unspecified ELSE SUCC[precisionOfResult]] -- if user says in one hour, he probably means to nearest minute. ELSE IF before THEN Error[notImplemented] -- Thursday before May 5 not implemented. two days before May 5 is. ELSE [time, precision] _ SmartPack[year: year, month: month, day: day, hour: hour, minute: minute, second: second, weekday: weekday, baseTime: baseTime ]; RETURN[time, precision, start, length]; END -- block for errors. }; IsDayOfWeek: PROCEDURE [token: Rope.ROPE] RETURNS [DayOfWeek] = { RETURN[SELECT TRUE FROM RopeEqual[token, "Monday"], RopeEqual[token, "Mon"] => Monday, RopeEqual[token, "Tuesday"], RopeEqual[token, "Tues"] => Tuesday, RopeEqual[token, "Wednesday"], RopeEqual[token, "Wed"] => Wednesday, RopeEqual[token, "Thursday"], RopeEqual[token, "Thurs"] => Thursday, RopeEqual[token, "Friday"], RopeEqual[token, "Fri"] => Friday, RopeEqual[token, "Saturday"], RopeEqual[token, "Sat"] => Saturday, RopeEqual[token, "Sunday"], RopeEqual[token, "Sun"] => Sunday, ENDCASE => unspecified]; }; IsMonthOfYear: PROCEDURE [token: Rope.ROPE] RETURNS [MonthOfYear] = { RETURN[SELECT TRUE FROM RopeEqual[token, "January"], RopeEqual[token, "Jan"] => January, RopeEqual[token, "February"], RopeEqual[token, "Feb"] => February, RopeEqual[token, "March"], RopeEqual[token, "Mar"] => March, RopeEqual[token, "April"], RopeEqual[token, "Apr"] => April, RopeEqual[token, "May"] => May, RopeEqual[token, "June"], RopeEqual[token, "Jun"] => June, RopeEqual[token, "July"], RopeEqual[token, "Jul"] => July, RopeEqual[token, "August"], RopeEqual[token, "Aug"] => August, RopeEqual[token, "September"], RopeEqual[token, "Sep"], RopeEqual[token, "Sept"] => September, RopeEqual[token, "October"], RopeEqual[token, "Oct"] => October, RopeEqual[token, "November"], RopeEqual[token, "Nov"] => November, RopeEqual[token, "December"], RopeEqual[token, "Dec"] => December, ENDCASE => unspecified]; }; RopeEqual: PROC [r1, r2: ROPE] RETURNS [BOOL] = INLINE { RETURN[Rope.Equal[r1, r2, FALSE]]; }; MakeRope: PUBLIC PROCEDURE [time: Packed _ defaultTime, precision: Precision _ minutes, includeDayOfWeek: BOOL _ FALSE, useAMPM: BOOL _ TRUE] RETURNS[value: Rope.ROPE] = { unpacked: Unpacked _ Unpack[time]; IF includeDayOfWeek AND precision >= days THEN value _ Rope.Concat[ SELECT unpacked.weekday FROM Monday => "Monday", Tuesday => "Tuesday", Wednesday => "Wednesday", Thursday => "Thursday", Friday => "Friday", Saturday => "Saturday", Sunday => "Sunday", ENDCASE => ERROR, ", "]; IF precision >= months THEN value _ Rope.Cat[ value, SELECT unpacked.month FROM January => "January ", February => "February ", March => "March ", April => "April ", May => "May ", June => "June ", July => "July ", August => "August ", September => " September ", October => "October ", November => " November ", December => "December ", ENDCASE => ERROR, IF precision >= days THEN Rope.Concat[Convert.ValueToRope[[signed[unpacked.day]]], ", "] ELSE "" ]; value _ Rope.Concat[ value, Convert.ValueToRope[[signed[unpacked.year]]] ]; IF precision >= hours THEN { value _ Rope.Cat[ value, " ", Convert.ValueToRope[[signed[ IF NOT useAMPM THEN unpacked.hour ELSE IF unpacked.hour < 13 THEN unpacked.hour ELSE unpacked.hour - 12]]]]; IF precision >= minutes THEN { value _ Rope.Cat[ value, ":", IF unpacked.minute < 10 THEN "0" ELSE "", Convert.ValueToRope[[signed[unpacked.minute]]] ]; IF precision >= seconds THEN value _ Rope.Cat[ value, ":", IF unpacked.second < 10 THEN "0" ELSE "", Convert.ValueToRope[[signed[unpacked.second]]], ]; }; IF useAMPM THEN value _ Rope.Cat[ value, IF unpacked.hour >= 12 THEN " pm" ELSE " am" ]; }; }; END. ฒTempusImpl.mesa; Edited by Teitelman on July 20, 1983 2:48 pm DateAndTime USING [Parse, Unintelligible], Types Low Level Procedures inverse of Unpack, ignores redundant fields. Raises Invalid if given invalid data, e.g. February 30. "Smart" Procedures e.g. if it is now 4PM on the 15th, and user said the 15th at 2PM, will increment month if it is now 4PM on April 15th, and user said April 15th at 2PM, will increment year if it is now 4:30PM, and user said 4 PM, increments day, if it is now 4:30PM on the 15th, and user said the 15th at 4 PM, increments month if it is now 4:30PM on April 15th, and user said April 15th at 4PM, increments year The reason for the asymmetry between months/days and hours/minutes/seconds, i.e. the reason why don't use IncrementHours for hours, is because of daylight savings time. If the baseTime is midnight on Sunday of the day that daylight savings time changes, and you say increment 3 hours, I claim the user means 3 elapsed hours, i.e. 4AM. If you simply add 3 to unpacked.hour and call Pack, it will happily return 3AM, because that is a valid time. ELSE IF x _ Spell.Choose[[minutes, seconds, etc.] IF hour = hoursPerDay THEN hour _ 12 ELSE IF hour # 12 THEN Error[overConstrained]; the current token is a number, what does it mean. ! Error => {why _ ec; time _ defaultTime; CONTINUE}; Miscellaneous IF RopeInline.InlineSize[r1] # RopeInline.InlineSize[r2] THEN RETURN[FALSE] causes compiler overflow ส!ฬ– "Cedar" style˜Jšฯc(œ™=J˜šฯk ˜ Jšœžœ˜)Jšœ žœ™,Jšžœžœžœqžœ˜†Jšœžœžœ,˜J˜!Jšœžœ˜Jšœžœžœ˜J˜+Jšœ žœ+˜?Jšœžœ˜J˜J˜Jšœ ˜ J˜JšžœF˜MJšžœ/žœ˜Ršž˜J˜J˜Jšžœ žœžœ!žœ˜Jšžœžœ x˜——J˜ Jšœžœ˜Jšœ#˜#Jšžœ˜šžœ˜Jšœ žœ˜+—J˜J˜Jšœ˜Jšœ ˜ J˜šžœ(žœ˜0Jšœ ˜ Jšœ žœ˜Jšœ˜—šžœ*žœ˜7Jšœ ˜ Jšœ žœ˜J˜—šžœžœžœž˜šœ"˜"Jšžœžœ˜-Jšžœ žœ˜!Jšœ žœ˜J˜—šœ#˜#J˜@Jšœ˜Jšœ˜Jšœ ˜ Jšœ žœ˜J˜—šœ˜Jšœ@˜RJšœ˜Jšœ˜Jšœ žœ˜J˜—šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ žœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ žœ˜J˜—Jšœ:žœ žœ˜PJšœ9žœ žœ˜Ošœ$ž œ ˜;šžœž˜&Jšœžœ˜ Jšœฬ˜฿Jšœ*˜*J˜—Jšžœ ˜J˜—šœ*žœ ˜;šžœž˜&Jšœžœ˜ Jšœ˜Jšœ*˜*J˜—Jšžœ ˜J˜—šœ˜Jšœ ˜ Jšžœžœ ™$Jšžœžœ žœ™.Jšœžœ˜ Jšœ žœ˜Jšœ˜—Jšžœžœ ˜—Jšœ#˜#Jšžœ˜šžœ˜Jšœ žœ˜+—J˜—˜Jšž˜šžœžœž˜Jšœ'˜'J˜Jšœ'˜'J˜Jšœ(˜(J˜Jšœ,˜,J˜Jšžœ ˜—Jšœ#˜#Jšžœ˜šžœ˜Jšœ žœ˜+—J˜J˜šžœžœž˜š œSžœžœ žœ žœ5˜ถJ˜Jšœ$žœžœ"˜_J˜Jšžœ!žœžœ˜gJšžœžœ>˜hJšžœžœ˜1Jšžœžœ˜3Jšžœžœ žœ˜1Jšžœ˜Jšœ˜—šœ2ž˜3Jšœ#˜#JšžœD˜LJ˜—šœGž˜HJšœ#˜#Jšžœ˜Jšœ˜—Jšžœ˜—J˜˜J˜J˜Jšœ;žœ˜EJšžœžœžœ ˜Jšœ1™1Jšžœžœ D˜eJš žœžœžœžœžœ˜XJš žœžœžœ žœžœ˜Yš žœžœžœ žœ˜Kšžœ žœ3˜GJšžœžœžœ-˜^Jš žœžœžœžœ˜ZJšžœžœ˜˜KJšžœžœžœ˜šžœ žœ ž˜!Jšžœ žœ žœž˜ Jšžœžu˜€Jšœ˜—Jšžœžœžœžœ.˜EJšžœžœ˜'——J˜—J˜J˜J˜Jšžœ˜—J˜Jšœ˜J˜Jšžœžœ žœžœ žœžœžœJ˜›šžœžœžœ˜5Jš œžœ žœžœžœžœžœ˜?Jš œžœ žœžœžœžœ žœ˜CJš œžœžœžœžœžœžœ˜;Jš œžœ žœžœžœžœžœ˜?Jš œ žœ žœžœžœžœ žœ ˜GJš œ žœ žœžœžœžœ žœ ˜GJšœ˜Jš œžœ!žœ žœžœA˜ง—JšžœžœžœC˜nšžœ“˜—Jšœ žœž œ™5Jšœ˜—Jšžœ!˜'J™Jšžœ˜Jšœ˜—J˜šก œž œžœžœ˜Ašž œžœž˜Jšœ>˜>JšœA˜AJšœD˜DJšœD˜DJšœ>˜>JšœB˜BJšœ?˜?Jšžœ˜—Jšœ˜—J˜šก œž œžœžœ˜Ešž œžœž˜Jšœ@˜@JšœB˜BJšœ<˜