Portfolio...Alternative Date Entry using Clipper

Originally published in Grumpfish Aquarium magazine in March of 94
(You can use the NaturalDate library to achieve similar functionality in .NET)

Imagine the following conversation between two friends attempting to schedule a tennis match:

     Ken:  "How about Friday?"
     Joe:  "No, Friday is no good, how about tomorrow?"
     Ken:  "Tomorrow is out, is Thursday alright?"
     Joe:  "No, next Monday is open!"
     Ken:  "Ok, I will get a court for next Monday!"

Now imagine that same conversation using CA-Clipper to handle the date portions:

     Ken:   "How about 01/14/94?"
     Joe:   "No, 01/14/94 is no good, how about 01/11/94?"
     Ken:   "01/11/94 is out, is 01/13/94 alright?"
     Joe:   "No, 01/17/94 is open!"
     Ken:   "Ok, I will get a court for 01/17/94!"

While the second conversation is much more precise, it also is not very easy to do without a calendar in front on you. Why do we then make our end-users enter dates in this fashion? (if you answer 'Because we don't like them!', then do not bother reading this article.)

Entering GuessDate()

Clipper provides a wide variety of date functions and powerful date arithmetic. Using the functions from Clipper, we can easily write an easy to use date conversion function, so the first conversation could be handled in Clipper. In this article, we will provide such an option. Its name is GuessDate() and what is does is attempt to interpret the user's input as a valid date. The syntax of GuessDate() is:

    <dActual> := GuessDate(cInput)

where is the string the user entered, and <dActual> is the date that it represents. If cInput cannot be interpreted clearly, then <dActual> will contain an empty date. This allows you to use the function as part of a VALID clause, as shown:

    cInput := space(25)
    dDate  := ctod( '  /  /  ' )
    @ 09,10 SAY "When is the meeting scheduled? "  ;
            GET cInput  VALID !empty( dDate := GuessDate(cInput) )
    READ

When you are using this routine, keep in mind that the routine is making a guess as to what the user is trying to say. Once you are done, be sure to show the user the date that they have entered. It is possible that our guess has come up with a valid date, but not the one the user had in mind. So allow the user to correct the date if they did not quite get what they expected.

What To Handle - The Easy Part

The GuessDate() function handles a variety of inputs. For starters, the function handles regular old date entry, such as mm/dd/yy. It does this by taking the input parameter and passing it to Clipper's CTOD() function. If CTOD() can evaluate it and produce a date, then GuessDate() uses that date; there is little sense in duplicating what Clipper can already do.

In addition, GuessDate() recognizes certain keywords. For example, yesterday, today, and tomorrow all return valid dates. So do Christmas, Easter, and Thanksgiving. These key words are easy to recognize. Christmas is easy to deal with, since it always falls on the same date.

What To Handle - Tricky parts

Easter is a little trickier, so I use the code from the FT_Easter() function of the Nanforum Toolkit to determine the date that Easter falls on. The calculation for deriving Easter was done by Donald Knuth in his books on algorithm. If you look at the algorithm, you will conclude that Donald has way too much time on his hands!

For Thanksgiving, I wrote a function called FindDay(). This function takes a month number, the day of the year you are looking for, the year, and the occurrence. Since Thanksgiving is always the fourth Thursday of November in the United States and the second Monday in October in Canada, the call:

    dActual   := FindDay(  11, ;    // Month of November
                            5, ;    // Thursday
                         1994, ;    // Year
                            4 )     // Fourth occurence

Will return the date that Thanksgiving falls on. Labor day is handled in a similar manner.

Finally, the GuessDate() function will recognize day names, such as Tuesday or Friday. So if you call GuessDate() on Wednesday, January 12, 1994 and pass it Friday, it will return the date 01/14/94.

You can easily expand the list of single word to handle things like Standard or Rush. For example:

    @ 10,10 SAY "Delivery date: ";
            GET cDate VALID (dDate := GuessDate(cDate) )

If the user enters STANDARD, then dDate might contain date()+7 while for an entry of RUSH, dDate might contain date() +3. OVERNIGHT could produce a value of date()+1, and so on. This truly makes the system simple for the end user to understand.

What To Handle - The Harder Part

In addition to single word entries, GuessDate() also handles two word entries. For example, 'Next Tuesday' would return 01/18/94 if you typed it on Monday, January 10, 1994. It also recognizes the modifier 'Last' and 'This', so items like 'Next Friday' or 'This Easter' or 'Last Thanksgiving' all are acceptable.

To handle two word entries, GuessDate() breaks the input into two words, using a space as the separation character. If the first word is 'THIS', 'LAST', or 'NEXT', it is considered a modifier. The second word determines the date and the modifier changes it. 'Next Easter' will call the FT_Easter() function with next year instead of the current year. 'Next Monday' will add seven days to the Monday that it finds. 'Last Tuesday' will work backwards from the current date.

In addition, the following two word inputs are recognized:

You can easily modify the GuessDate() function to handle other key dates.

Summary

While it requires a bit more code and introduces ambiguities that might not exist otherwise, writing software that talks the user's language makes your program much easier to use. Unfortunately, you can sometimes make things too easy, because my users are always typing in 'Friday' or 'Next Weekend' to their contact managers, which display 'Invalid date'. Oh well, maybe someday date entry will become standardized...