DIRECTORY Ascii USING [Digit, Letter, Lower], BasicTime USING [daysPerMonth, DayOfWeek, earliestGMT, GMT, minutesPerHour, secondsPerMinute, MonthOfYear, Now, Pack, Unpack, Unpacked, Update], Convert USING [IntFromRope, Error], Rope USING [Fetch, Find, IsEmpty, Length, ROPE, SkipTo, Substr], TimeParse; TimeParseImpl: CEDAR PROGRAM IMPORTS Ascii, BasicTime, Convert, Rope EXPORTS TimeParse = BEGIN OPEN TimeParse; AmPm: TYPE = {am, pm}; shortMonths: ARRAY[0..11] OF Rope.ROPE; months: ARRAY[0..11] OF Rope.ROPE = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; shortWeekdays: ARRAY[0..6] OF Rope.ROPE; otherWeekdays: ARRAY[0..6] OF Rope.ROPE ¬ ALL[""]; weekdays: ARRAY[0..6] OF Rope.ROPE = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; ParseError: PUBLIC SIGNAL[errorType: ParseErrorType] = CODE; MakeArrays: PROC[] = { FOR i: INT IN [0..11] DO shortMonths[i] ¬ Rope.Substr[months[i], 0, 3]; ENDLOOP; FOR i: INT IN [0..6] DO shortWeekdays[i] ¬ Rope.Substr[weekdays[i], 0, 3]; ENDLOOP; otherWeekdays[1] ¬ "Tues"; otherWeekdays[3] ¬ "Thurs"; }; Parse: PUBLIC PROC[ str: Rope.ROPE, now: BasicTime.GMT, direction: DirectionType ¬ heuristic, insistTime: BOOLEAN ¬ TRUE, insistDay: BOOLEAN ¬ TRUE] RETURNS [time: BasicTime.GMT, pieces: PiecesType ¬ NIL] = { offset: INT; hr: INT ¬ -1; min: INT ¬ 0; year, tmpYr: INT; month: BasicTime.MonthOfYear; weekday: BasicTime.DayOfWeek; day, wday: INT ¬ -1; past, maybe: BOOLEAN ¬ FALSE; noAmPm: BOOLEAN ¬ FALSE; unp: BasicTime.Unpacked; MatchAmPm: PROC [str: Rope.ROPE, k: INT ¬ 0] RETURNS [found: BOOL, which: AmPm, start, stop: INT] = { ln: INT; ch: CHAR; found ¬ FALSE; ln ¬ Rope.Length[str]; WHILE k < Rope.Length[str] DO start ¬ k ¬ Rope.SkipTo[str, k, "aApP"]; IF k < ln-1 AND (ch ¬ Rope.Fetch[str, k+1]) = '. THEN k ¬ k + 1; IF k < ln-1 AND (ch ¬ Rope.Fetch[str, k+1]) = 'm OR ch = 'M THEN { IF (ch ¬ Rope.Fetch[str, start]) = 'a OR ch = 'A THEN which ¬ am ELSE which ¬ pm; found ¬ TRUE; stop ¬ k+1; IF Rope.Fetch[str, start+1] = '. THEN stop ¬ stop + 1; RETURN; }; k ¬ k + 1; ENDLOOP; }; SlashFormat: PROC[] RETURNS [ month: BasicTime.MonthOfYear ¬ unspecified, day, yr: INT ¬ -1 ] = { i, j, k, ln: INT; tmpDay, tmpMonth: INT; ln ¬ Rope.Length[str]; FOR i ¬ 0, Rope.Find[str, "/", i] UNTIL i = -1 DO IF i > 0 AND Ascii.Digit[Rope.Fetch[str, i-1]] AND i < ln + 1 AND Ascii.Digit[Rope.Fetch[str, i+1]] THEN { j ¬ i-1; IF j > 0 AND Ascii.Digit[Rope.Fetch[str, j-1]] THEN j ¬ j - 1; IF j > 0 AND AlNum[Rope.Fetch[str, j-1]] THEN {i ¬ i + 1; LOOP;}; k ¬ i + 1; IF k < ln - 1 AND Ascii.Digit[Rope.Fetch[str, k+1]] THEN k ¬ k + 1; IF k < ln-1 AND AlNum[Rope.Fetch[str, k+1]] THEN {i ¬ i + 1; LOOP;}; tmpDay ¬ Convert.IntFromRope[Rope.Substr[str, i+1, k - i]]; tmpMonth ¬ Convert.IntFromRope[Rope.Substr[str, j, i - j]]; IF tmpDay < 1 OR tmpDay > 31 OR tmpMonth < 1 OR tmpMonth > 12 THEN {i ¬ i + 1; LOOP;}; day ¬ tmpDay; month ¬ VAL[CARDINAL[tmpMonth-1]]; -- mm=1 means month is January IF k < ln - 3 AND Rope.Fetch[str, k+1] = '/ AND Ascii.Digit[Rope.Fetch[str, k+2]] AND Ascii.Digit[Rope.Fetch[str, k+3]] THEN { yr ¬ Convert.IntFromRope[Rope.Substr[str, k+2, 2]]; IF yr < 80 OR yr > 99 THEN SIGNAL ParseError[badYearInSlash]; yr ¬ 1900 + yr; k ¬ k + 3; }; pieces ¬ CONS[NEW[PieceType ¬ [j, k-j+1]], pieces]; RETURN; } ELSE i ¬ i + 1; ENDLOOP; }; MatchColonTime: PROC[] RETURNS [hr: INT ¬ -1, min: INT ¬ 0] = { ln, i, j: INT; ok: BOOLEAN; start, stop: INT; which: AmPm; i ¬ 0; ln ¬ Rope.Length[str]; WHILE (i ¬ Rope.Find[str, ":", i]) # -1 DO IF i >= 1 AND Ascii.Digit[Rope.Fetch[str, i-1]] AND i < ln-2 AND Ascii.Digit[Rope.Fetch[str, i+1]] AND Ascii.Digit[Rope.Fetch[str, i+2]] THEN { IF i >= 2 AND Ascii.Digit[Rope.Fetch[str, i-2]] THEN { hr ¬ Convert.IntFromRope[Rope.Substr[str, i-2, 2]]; j ¬ i-2; ln ¬ 5; } ELSE { hr ¬ Convert.IntFromRope[Rope.Substr[str, i-1, 1]]; j ¬ i-1; ln ¬ 4; }; min ¬ Convert.IntFromRope[Rope.Substr[str, i+1, 2]]; pieces ¬ CONS[NEW[PieceType ¬ [j, ln]], pieces]; [ok, which, start, stop] ¬ MatchAmPm[str, i+3 - ln]; IF ok THEN { pieces ¬ CONS[NEW[PieceType ¬ [start, stop - start + 1]], pieces]; IF hr = 12 THEN hr ¬ 0; IF which = pm THEN hr ¬ hr + 12; } ELSE noAmPm ¬ TRUE; }; i ¬ i + 1; ENDLOOP; }; MatchAmPmTime: PROC[] RETURNS [hr: INT ¬ -1] = { ok: BOOLEAN; which: AmPm; i, start, stop: INT; [ok, which, start, stop] ¬ MatchAmPm[str]; IF ok THEN { i ¬ start-1; WHILE i >= 0 AND Rope.Fetch[str,i] = ' DO i ¬ i - 1; ENDLOOP; WHILE i >= 0 AND Ascii.Digit[Rope.Fetch[str,i]] DO i ¬ i - 1; ENDLOOP; hr ¬ Convert.IntFromRope[Rope.Substr[str, i+1, (start-1) - (i+1) + 1] ! Convert.Error => {GOTO done;}]; IF hr >= 24 THEN hr ¬ -1 ELSE IF which = pm THEN hr ¬ hr + 12; --XXX: check to see if hr <= 12? IF hr >= 0 THEN pieces ¬ CONS[NEW[PieceType ¬ [i+1, stop - i]], pieces]; EXITS done => {}; }; }; MatchWord: PROC[word: Rope.ROPE] RETURNS [BOOLEAN] = { ln, k: INT; ln ¬ Rope.Length[word]; k ¬ 0; WHILE TRUE DO k ¬ Rope.Find[str, word, k, FALSE]; IF k = -1 THEN RETURN[FALSE]; IF (k = 0 OR ~Ascii.Letter[Rope.Fetch[str, k-1]]) AND (k + ln = Rope.Length[str] OR ~Ascii.Letter[Rope.Fetch[str, k+ln]]) THEN { pieces ¬ CONS[NEW[PieceType ¬ [k, ln]], pieces]; RETURN[TRUE]; }; k ¬ k + 1; ENDLOOP; RETURN[FALSE]; -- this is here to keep compiler happy }; AlNum : PROC [ch: CHAR] RETURNS [BOOL] = INLINE { RETURN [ch IN ['A..'Z] OR ch IN ['a..'z] OR ch IN ['0..'9]]; }; MatchYear: PROC[] RETURNS [year: INT] = { k, ln: INT; year ¬ -1; k ¬ 0; ln ¬ Rope.Length[str]; WHILE TRUE DO k ¬ Rope.Find[str, "19", k, FALSE]; IF k = -1 THEN RETURN; IF (k = 0 OR ~AlNum[Rope.Fetch[str, k-1]]) AND k < ln - 3 AND Ascii.Digit[Rope.Fetch[str, k+2]] AND Ascii.Digit[Rope.Fetch[str, k+3]] AND (k + 4 = ln OR ~Ascii.Letter[Rope.Fetch[str, k+4]]) THEN { year ¬ Convert.IntFromRope[Rope.Substr[str, k, 4]]; pieces ¬ CONS[NEW[PieceType ¬ [k, 4]], pieces]; RETURN; }; k ¬ k + 2; ENDLOOP; RETURN; -- this is here to keep compiler happy }; MatchMonth: PROC[] RETURNS [mnth: BasicTime.MonthOfYear] = { i: CARDINAL; FOR i IN [0..11] DO IF MatchWord[shortMonths[i]] THEN RETURN[VAL[i]]; ENDLOOP; FOR i IN [0..11] DO IF MatchWord[months[i]] THEN RETURN[VAL[i]]; ENDLOOP; RETURN[unspecified]; }; MatchWeekday: PROC[] RETURNS [BasicTime.DayOfWeek] = { i: CARDINAL; FOR i IN [0..6] DO IF MatchWord[shortWeekdays[i]] THEN RETURN[VAL[i]]; ENDLOOP; FOR i IN [0..6] DO IF MatchWord[weekdays[i]] THEN RETURN[VAL[i]]; ENDLOOP; FOR i IN [0..6] DO IF ~Rope.IsEmpty[otherWeekdays[i]] AND MatchWord[otherWeekdays[i]] THEN RETURN[VAL[i]]; ENDLOOP; RETURN[unspecified]; }; MatchTime: PROC[] RETURNS [INT] = { k, k1, ln, cnt, val: INT; ch: CHAR; ln ¬ Rope.Length[str]; k ¬ 0; WHILE TRUE DO k1 ¬ k ¬ Rope.SkipTo[str, k, "0123456789"]; cnt ¬ 1; IF k = ln THEN RETURN[-1]; IF k > 0 AND (AlNum[ch ¬ Rope.Fetch[str, k-1]] OR ch = ':) THEN {k ¬ k + 1; LOOP}; IF k < ln-1 AND Ascii.Digit[Rope.Fetch[str, k+1]] THEN {k ¬ k + 1; cnt ¬ 2;}; IF k = ln-1 OR (~AlNum[ch ¬ Rope.Fetch[str, k+1]] AND ch # ':) THEN { val ¬ Convert.IntFromRope[Rope.Substr[str, k1, cnt]]; IF val >= 1 AND val <= 12 THEN { pieces ¬ CONS[NEW[PieceType ¬ [k1, cnt]], pieces]; noAmPm ¬ TRUE; RETURN[val]; }; }; k ¬ k + 1; ENDLOOP; RETURN[-1]; -- this is here to keep compiler happy }; InPieces: PROC[i: INT] RETURNS [BOOLEAN] = { plist: PiecesType; plist ¬ pieces; WHILE plist # NIL DO IF i >= plist.first.start AND i < plist.first.start + plist.first.len THEN RETURN[TRUE]; plist ¬ plist.rest; ENDLOOP; RETURN[FALSE]; }; MatchDay: PROC[] RETURNS [INT] = { k, k1, ln, cnt, val: INT; ch: CHAR; b: BOOLEAN ¬ FALSE; Th: PROC[k: INT] RETURNS [b: BOOLEAN] = { IF k >= ln-2 THEN RETURN[FALSE]; SELECT Rope.Fetch[str, k] FROM '1 => b ¬ Ascii.Lower[Rope.Fetch[str, k+1]] = 's AND Ascii.Lower[Rope.Fetch[str, k+2]] = 't; '2 => b ¬ Ascii.Lower[Rope.Fetch[str, k+1]] = 'n AND Ascii.Lower[Rope.Fetch[str, k+2]] = 'd; '3 => b ¬ Ascii.Lower[Rope.Fetch[str, k+1]] = 'r AND Ascii.Lower[Rope.Fetch[str, k+2]] = 'd; ENDCASE =>b ¬ Ascii.Lower[Rope.Fetch[str, k+1]] = 't AND Ascii.Lower[Rope.Fetch[str, k+2]] # 'h; RETURN[b AND (k+2 = ln-1 OR ~AlNum[Rope.Fetch[str,k+3]])]; }; ln ¬ Rope.Length[str]; k ¬ 0; WHILE TRUE DO k1 ¬ k ¬ Rope.SkipTo[str, k, "0123456789"]; cnt ¬ 1; IF k = ln THEN RETURN[-1]; IF k > 0 AND (AlNum[ch ¬ Rope.Fetch[str, k-1]] OR ch = ':) THEN {k ¬ k + 1; LOOP}; IF InPieces[k] THEN {k ¬ k + 1; LOOP}; IF k < ln-1 AND Ascii.Digit[Rope.Fetch[str, k+1]] THEN {k ¬ k + 1; cnt ¬ 2;}; IF k = ln-1 OR (~AlNum[ch ¬ Rope.Fetch[str, k+1]] AND ch # ':) OR (b ¬ Th[k]) THEN { val ¬ Convert.IntFromRope[Rope.Substr[str, k1, cnt]]; IF val >= 1 AND val <= 31 THEN { IF b THEN {cnt ¬ cnt + 2;}; pieces ¬ CONS[NEW[PieceType ¬ [k1, cnt]], pieces]; RETURN[val]; }; }; k ¬ k + 1; ENDLOOP; RETURN[-1]; -- this is here to keep compiler happy }; MyAdjust: PROC[time: BasicTime.GMT, min, hr: INT, days: INT] RETURNS [BasicTime.GMT] = { unp: BasicTime.Unpacked; unp ¬ BasicTime.Unpack[time]; unp.minute ¬ min; unp.hour ¬ hr; time ¬ BasicTime.Pack[unp]; RETURN[Adjust[days: days, baseTime: time, precisionOfResult: minutes].time]; }; MonthDistance: PROC[a, b: BasicTime.MonthOfYear] RETURNS [k: INT] = { k ¬ 0; WHILE a # b DO IF a = December THEN a ¬ FIRST[BasicTime.MonthOfYear] ELSE a ¬ SUCC[a]; k ¬ k + 1; ENDLOOP; }; unp ¬ BasicTime.Unpack[now]; unp.second ¬ 0; unp.dst ¬ unspecified; -- we want it to float to the correct value for the result time IF MatchWord["noon"] THEN hr ¬ 12 ELSE [hr, min] ¬ MatchColonTime[]; IF hr < 0 THEN hr ¬ MatchAmPmTime[]; IF hr < 0 AND (hr ¬ MatchTime[]) # -1 THEN maybe ¬ TRUE; IF hr = -1 THEN { IF insistTime THEN SIGNAL ParseError[noTime] ELSE hr ¬ 0; }; IF MatchWord["today"] THEN { IF noAmPm AND hr # 0 AND hr < 12 THEN { IF direction # backward AND hr < unp.hour THEN hr ¬ hr + 12 ELSE IF direction = backward AND hr + 12 > unp.hour THEN NULL ELSE IF hr < 8 THEN hr ¬ hr + 12; }; time ¬ MyAdjust[now, min, hr, 0]; RETURN; } ELSE IF MatchWord["yesterday"] THEN { IF noAmPm AND hr # 0 AND hr < 8 THEN hr ¬ hr + 12; time ¬ MyAdjust[now, min, hr, -1]; RETURN; } ELSE IF MatchWord["tomorrow"] THEN { IF noAmPm AND hr # 0 AND hr < 8 THEN hr ¬ hr + 12; time ¬ MyAdjust[now, min, hr, 1]; RETURN; }; year ¬ MatchYear[]; [month, day, tmpYr] ¬ SlashFormat[]; IF month = unspecified THEN month ¬ MatchMonth[]; IF tmpYr # -1 AND year # -1 THEN SIGNAL ParseError[twoYears]; IF year = -1 THEN year ¬ tmpYr; weekday ¬ MatchWeekday[]; IF day = -1 THEN day ¬ MatchDay[]; IF day = -1 AND maybe THEN { IF ~insistTime THEN { day ¬ hr; hr ¬ 0; } ELSE { IF (month # unspecified OR year # -1) AND insistDay THEN SIGNAL ParseError[noTime]; }; }; BEGIN -- establish scope for EXITS clause IF weekday = unspecified AND day = -1 THEN { IF (month # unspecified OR year # -1) THEN { IF insistDay THEN SIGNAL ParseError[yearOrMonthButNoDay] ELSE day ¬ 1; } ELSE { IF noAmPm AND hr # 0 AND hr < 12 THEN { IF direction # backward AND hr < unp.hour THEN hr ¬ hr + 12 ELSE IF direction = backward AND hr + 12 > unp.hour THEN NULL ELSE IF hr < 8 THEN hr ¬ hr + 12; }; past ¬ 60*hr + min < 60*unp.hour + unp.minute; unp.minute ¬ min; unp.hour ¬ hr; time ¬ BasicTime.Pack[unp]; SELECT direction FROM forward, heuristic => IF past THEN time ¬ Adjust[baseTime: time, days: 1, precisionOfResult: minutes].time; backward => IF ~past THEN time ¬ Adjust[baseTime: time, days: -1, precisionOfResult: minutes].time; ENDCASE => {}; GOTO out; }; }; IF noAmPm THEN { IF hr < 8 AND hr # 0 THEN hr ¬ hr + 12; }; IF day = -1 AND weekday # unspecified THEN { IF month # unspecified OR year # -1 THEN SIGNAL ParseError[yearOrMonthButNoDay]; BEGIN -- establish scope for GOTO SELECT direction FROM forward, heuristic => IF unp.weekday # weekday OR 60*hr + min < 60*unp.hour + unp.minute THEN offset ¬ 1; backward => IF unp.weekday # weekday OR 60*hr + min > 60*unp.hour + unp.minute THEN offset ¬ -1; ENDCASE => {GOTO nochange;}; WHILE unp.weekday # weekday DO now ¬ Adjust[baseTime: now, days: offset, precisionOfResult: minutes].time; unp ¬ BasicTime.Unpack[now]; ENDLOOP; EXITS nochange => {}; END; unp.minute ¬ min; unp.hour ¬ hr; time ¬ BasicTime.Pack[unp]; GOTO out; }; IF month = unspecified THEN { IF year # -1 THEN SIGNAL ParseError[yearButNoMonth]; past ¬ day < unp.day OR (day = unp.day AND 60*hr + min < 60*unp.hour + unp.minute); offset ¬ 0; SELECT direction FROM forward, heuristic => IF past THEN offset ¬ 1; backward => IF ~past THEN offset ¬ -1; ENDCASE => {}; IF offset = 0 THEN { unp.minute ¬ min; unp.hour ¬ hr; unp.day ¬ day; } ELSE { time ¬ Adjust[baseTime: now, months: offset, precisionOfResult: minutes].time; unp ¬ BasicTime.Unpack[time]; unp.minute ¬ min; unp.hour ¬ hr; unp.day ¬ day; }; time ¬ BasicTime.Pack[unp]; GOTO out; }; IF year = -1 THEN { offset ¬ MonthDistance[month, unp.month]; SELECT direction FROM forward => time ¬ Adjust[baseTime: now, months: 12 - offset, precisionOfResult: minutes].time; heuristic => IF offset <= 1 THEN time ¬ Adjust[baseTime: now, months: -offset, precisionOfResult: minutes].time ELSE time ¬ Adjust[baseTime: now, months: 12 - offset, precisionOfResult: minutes].time; backward => time ¬ Adjust[baseTime: now, months: -offset, precisionOfResult: minutes].time ENDCASE => {}; unp ¬ BasicTime.Unpack[time]; unp.minute ¬ min; unp.hour ¬ hr; unp.day ¬ day; time ¬ BasicTime.Pack[unp]; GOTO out; }; IF year # -1 THEN { unp.minute ¬ min; unp.hour ¬ hr; unp.day ¬ day; unp.month ¬ month; unp.year ¬ year; time ¬ BasicTime.Pack[unp]; GOTO out; }; EXITS out => {}; END; IF weekday # unspecified THEN { unp ¬ BasicTime.Unpack[time]; IF unp.weekday # weekday THEN SIGNAL ParseError[dayWeekdayMismatch]; }; }; TempusParse: PUBLIC PROC[ rope: Rope.ROPE, baseTime: BasicTime.GMT, search: BOOLEAN ¬ TRUE] RETURNS [time: BasicTime.GMT, precision: Precision, start, length: NAT] = { time ¬ Parse[rope, baseTime].time; start ¬ length ¬ 0; precision ¬ unspecified; }; 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: BasicTime.GMT ¬ BasicTime.earliestGMT, precisionOfResult: Precision ¬ unspecified] RETURNS [time: BasicTime.GMT, precision: Precision] = { unpacked: BasicTime.Unpacked ¬ BasicTime.Unpack[IF baseTime = BasicTime.earliestGMT THEN BasicTime.Now[] ELSE 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 .. BasicTime.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 .. BasicTime.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; unpacked.dst ¬ BasicTime.Unpack[BasicTime.Pack[unpacked]].dst; --set dst properly before actual packing time ¬ BasicTime.Pack[unpacked]; IF hours # LAST[INT] THEN time ¬ BasicTime.Update[time, (hours * BasicTime.minutesPerHour * BasicTime.secondsPerMinute)]; IF minutes # LAST[INT] THEN time ¬ BasicTime.Update[time, minutes * BasicTime.secondsPerMinute]; IF seconds # LAST[INT] THEN time ¬ BasicTime.Update[time, seconds]; RETURN[time, precisionOfResult]; }; MakeArrays[]; END. RTimeParseImpl.mesa Copyright Ó 1989, 1992 by Xerox Corporation. All rights reserved. David Goldberg April 28, 1989 2:37:11 pm PDT Chauser, April 3, 1991 11:48 am PST Willie-s, May 5, 1992 2:09 pm PDT Parse scans str for a date and returns the time and a list of 'pieces' that specify where in the string the date appeared. Thus a calendar program (for example) could strip the date part out of the string If date is ambiguous and direction = forward(backward), then times will be interpreted in the future(past) relative to now. If direction=heuristic, then something (hopefully logical) is done. If insistTime is true, Parse assumes a time is specified somewhere in str. Algorithm used by Parse: Look for noon, midnight else look for digit:digit: . else Look for digit followed by pm, p.m., PM, P.M. am, a.m., AM, A.M. else if there must be a time (insistTime = TRUE), look for number between 1 and 12 otherwise error Heuristic: unqualified times < 8 are assumed to be pm, otherwise am (direction doesn't apply to time: should it?) Look for yesterday, today, tomorrow. If so, done Look for 19xx: If so this is the year Look for mm/dd where 1 <= mm <= 12 and 1 <= dd <= 31 If no month, look for Jan, January, Feb, February, etc..... If so, this is the month. Look for Mon, Monday, Tue, Tuesday, etc...: (tues, th, thur, ??) if so, this is the day If no mm/dd, Look for digit or digitdigit in range between 1 and 31. if so, this is the day: If more than 1 such number, pick the one adjacent to another time element. Finally, compute date (see below for this algorithm) Look for a.m. or p.m. look for mm/dd or mm/dd/yy. American convention, where month comes first look for things like 4:45, as well as optional trailing am/pm Next look for things like 4pm look for word as a token in str Search for 19xx if can't find colontime (e.g. 4:45) or number followed by am/pm (e.g. 2pm) look for a single number (like "go home at 4"). watch out for x:yy, in which case neither x nor yy is a date. However 9th and 23rd are dates This is the one place where we check that the substr we are hunting for has not been already found and thus in pieces. This is because digits like 4 are ambiguous: "Sep 4" vs "Meet today at 4" return true for 1st, 2nd 3rd, nth set time to hr:min, and then adjust by days MonthOfYear.Last isn't December, but rather is unspecified Look for a time. First check for 'noon' Next check for things like 4:45 Next look for things like 4pm Finally check for things like "go home at 4" If there is a relative day (yesterday, today, tomorrow) then date is complete check for a year, month, weekday, and day If string was "sep 4", the 4 was interpreted as a time above (at the point that maybe _ TRUE). Check for this situation now duplicate error check here to get more intelligible error return (otherwise yearOrMonthButNoDay will be raised later). BasicTime.Now compute date. Algorithm is if day and weekday unspecified then if month or yr specified then error if time < now then day _ today + 1 else day _ today (unless direction=backward) done if day unspecified then if month or yr specified then error day is smallest such that (time, week) > now ( or < now if direction = backward) done if month unspecified then if year specified then error if (day, time) < today then month _ thismonth + 1; (or thismonth -1 if direction=backward) done if year unspecified then if direction=heuristic: if month >=11 months past now, assume last month If weekday specified, check that it is consistent with date XXX: plausibility rule: unqaulified hr < 8 must mean pm. If it's now 2 pm Wed and somebody says 10:00 am Wed, do they really mean next wed? go back a month go forward 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 BasicTime.Pack, it will happily return 3AM, because that is a valid time. ÊO•NewlineDelimiter –(cedarcode) style˜code™Kšœ Ïeœ7™BKšœ,™,K™#K™!K™—šÏk ˜ Kšœžœ˜#Kšœ žœ&žœV˜Kšœžœ˜#Kšœžœ žœ˜@Kšœ ˜ K˜—KšÏn œžœž˜šž˜Kšœ˜—šž˜Kšœ ˜ —K˜Kšœžœžœ ˜K˜Kšœžœ ˜K˜Kšœ žœžœžœ˜'Kšœžœžœžœ~˜ŸKšœžœžœžœ˜(Kš œžœžœžœžœ˜2Kšœ žœžœžœR˜tK˜KšŸ œžœžœžœ˜Kšžœžœžœ žœ˜AK˜ Kšžœ žœ#žœ ˜CKšžœ žœžœ žœ˜DK˜;K˜;š žœ žœ žœžœž˜BKšœ žœ˜—K˜ KšœžœžœÏc˜Aš žœ žœžœ#žœ#ž˜~K˜3Kšžœ žœ žœžœ˜=K˜K˜ K˜—Kšœ žœžœ"˜3Kšžœ˜Kšœ˜—Kšžœ ˜Kšžœ˜—Kšœ˜—K™Kšœ=™=š Ÿœžœžœžœ žœ ˜?Kšœ žœ˜Kšœžœ˜ Kšœ žœ˜Kšœ ˜ K˜K˜K˜šžœ#ž˜*š žœžœ#žœ žœ#žœ#žœ˜šžœžœ#žœ˜6K˜3K˜K˜K˜—šž˜K˜3K˜K˜K˜—K˜4Kšœ žœžœ˜0K˜4šžœžœ˜ Kšœ žœžœ1˜BKšžœ žœ˜Kšžœ žœ˜ K˜—šž˜Kšœ žœ˜—K˜—K˜ Kšžœ˜—Kšœ˜—K™Kšœ™šŸ œžœžœžœ ˜0Kšœžœ˜ K˜ Kšœžœ˜K˜K˜*šžœžœ˜ K˜ Kšžœžœžœ žœ˜>Kšžœžœ žœ žœ˜F˜EKšœžœ ˜!—Kšžœ žœ˜Kšžœžœ žœ¡ ˜FKšžœ žœ žœžœ'˜Hšž˜K˜ —K˜—K˜—K˜Kšœ™š Ÿ œžœ žœžœžœ˜6Kšœžœ˜ K˜K˜K˜šžœžœž˜ Kšœžœ˜#Kšžœžœžœžœ˜š žœžœ&žœžœ'žœ˜€Kšœ žœžœ˜0Kšžœžœ˜ K˜—K˜ Kšžœ˜—Kšžœžœ¡&˜5Kšœ˜—K˜š Ÿœžœžœžœžœ˜&Kšœžœžœžœ žœžœ žœžœ˜K—J™Jšœ™šŸ œžœžœžœ˜)Kšœžœ˜ K˜K˜ K˜K˜šžœžœž˜ Kšœžœ˜#Kšžœžœžœ˜šžœžœžœ žœ#žœ#žœ žœ&žœ˜ÄK˜3Kšœ žœžœ˜/Kšžœ˜K˜—K˜ Kšžœ˜—Kšžœ¡&˜.Kšœ˜—K˜šŸ œžœžœ"˜