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 ← BasicTime.Unpack[IF baseTime = defaultTime THEN BasicTime.Now[] ELSE 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
};
desired.hour ← 2; -- we haven't set a time yet, but force it to 2:00 to avoid rolling back date on daylight savings time due to Pack/Unpack
desired.dst ← BasicTime.Unpack[BasicTime.Pack[desired]].dst;
IF weekday # unspecified
THEN {
ComputeDay:
PROC [currentWeekDay: DayOfWeek] = {
t: Unpacked ← BasicTime.Unpack[BasicTime.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;
};
desired.hour ← 2; -- avoid the daylight savings shift into the previous day!!!
SELECT precision
FROM
years => Error[tooVague];
months => {
-- means such day in corresponding month
t: Unpacked ← BasicTime.Unpack[BasicTime.Pack[desired]];
ComputeDay[t.weekday];
};
unspecified => {
ComputeDay[desired.weekday];
};
days => {
time ← BasicTime.Pack[desired];
desired ← BasicTime.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[];
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
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[];
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
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 ← BasicTime.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 ← BasicTime.Unpack[IF baseTime = defaultTime 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..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;
unpacked.dst ← BasicTime.Unpack[BasicTime.Pack[unpacked]].dst; --set dst properly before actual packing
time ← BasicTime.Pack[unpacked];
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.
IF hours # LAST[INT] THEN time ← BasicTime.Update[time, (hours * minutesPerHour * secondsPerMinute)];
IF minutes # LAST[INT] THEN time ← BasicTime.Update[time, minutes * secondsPerMinute];
IF seconds # LAST[INT] THEN time ← BasicTime.Update[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 => 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..12] 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, isAnImpliedDate, 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];
base: Packed ← IF baseTime = defaultTime THEN BasicTime.Now[] ELSE baseTime;
now: Unpacked ← BasicTime.Unpack[base];
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 token ← IO.GetTokenRope[stream, break ! IO.EndOfStream => EXIT].token;
{
SELECT
TRUE
FROM
RopeEqual[token, "\""] => {
stream.Backup['\"];
token ← prevToken;
nextToken ← stream.GetRopeLiteral[ ! IO.Error, IO.EndOfStream => EXIT];
LOOP;
};
RopeEqual[token, "years"], RopeEqual[token, "year"], RopeEqual[token, "yrs"] => {
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"], RopeEqual[token, "hrs"] => {
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"], RopeEqual[token, "mins"] => {
IF prevNum # -1 THEN minutes ← prevNum
ELSE IF Rope.Equal[prevToken, "a"] THEN minutes ← 1
ELSE Error[invalid];
precisionOfResult ← minutes;
};
RopeEqual[token, "seconds"], RopeEqual[token, "secs"] => {
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 x ← Spell.Choose[[minutes, seconds, etc.]
ELSE IF IsAnHour[prevNum] OR IsAMinute[prevNum] OR IsASecond[prevNum] THEN {prevNum ← -1; GOTO NextBlock}
ELSE IF isAnInterval OR isADate OR isAnImpliedDate 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, "Doomsday"] => {
base ← BasicTime.latestGMT;
isAnImpliedDate ← TRUE;
isATime ← TRUE;
};
RopeEqual[token, "Tomorrow"] => {
IF now.weekday = Sunday THEN weekday ← Monday
ELSE weekday ← SUCC[now.weekday];
isAnImpliedDate ← TRUE;
};
RopeEqual[token, "Yesterday"] => {
t: Unpacked ← BasicTime.Unpack[Adjust[days: -1, baseTime: base].time];
year ← t.year;
month ← t.month;
day ← t.day;
isAnImpliedDate ← 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;
isAnImpliedDate ← TRUE;
};
RopeEqual[token, "now"] => {
year ← now.year;
month ← now.month;
day ← now.day;
isAnImpliedDate ← TRUE;
hour ← now.hour;
minute ← now.minute;
second ← now.second;
IF hour < 12 THEN am ← TRUE;
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;
IF hour = hoursPerDay THEN hour ← 12
ELSE IF hour # 12 THEN Error[overConstrained];
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 isAnImpliedDate
OR isATime
THEN {
-- e.g. two weeks from Thursday, Thursday after May 1
precision: Precision;
[base, precision] ← Parse[rope: IO.GetTokenRope[stream, Everything].token, baseTime: base];
now ← BasicTime.Unpack[base];
IF precisionOfResult = unspecified OR precision > precisionOfResult THEN precisionOfResult ← precision;
IF hour # hoursPerDay THEN hours ← hour; -- e.g. 11:45 a week from Thursday. base 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 ! Convert.Error, SafeStorage.NarrowFault => CONTINUE];
{
-- test for a number followed by an ordinal abbreviation, like 1st, 2nd, 3rd, 17th
index: INT;
IF (index ← Rope.Find[s1: token, s2: "th", case: FALSE]) # -1 AND (index + 2) = Rope.Length[token] THEN token ← Rope.Substr[base: token, len: index]
ELSE IF (index ← Rope.Find[s1: token, s2: "nd", case: FALSE]) # -1 AND (index + 2) = Rope.Length[token] THEN token ← Rope.Substr[base: token, len: index]
ELSE IF (index ← Rope.Find[s1: token, s2: "rd", case: FALSE]) # -1 AND (index + 2) = Rope.Length[token] THEN token ← Rope.Substr[base: token, len: index]
ELSE IF (index ← Rope.Find[s1: token, s2: "st", case: FALSE]) # -1 AND (index + 2) = Rope.Length[token] THEN token ← Rope.Substr[base: token, len: index];
i ← Convert.IntFromRope[token ! Convert.Error, SafeStorage.NarrowFault => CONTINUE];
};
IF i = -1 THEN GOTO ExitCheck;
the current token is a number, what does it mean.
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.GetTokenRope[stream, break ! IO.EndOfStream => CONTINUE].token;
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 ← NIL;
nextToken ← IO.GetTokenRope[stream, break ! IO.EndOfStream => CONTINUE].token;
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 isAnImpliedDate
OR isATime
THEN {
IF (isADate OR isAnImpliedDate) 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 isAnImpliedDate AND NOT isAnInterval THEN Error[nothingSpecified] -- exited because ran out of characters in rope, or because search = FALSE
ELSE IF isADate AND isAnImpliedDate THEN Error[overConstrained]
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: base,
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: base
! Error => {why ← ec; time ← defaultTime; CONTINUE};
];
RETURN[time, precision, start, length];
END -- block for errors.
};
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];
};