Fun with UDFs

by Richard Bunter

This month, I'm bringing you one string function  and two
functions to support date handling.  I hope that these will  be
of use to you, and, if you're learning to program, be
illustrative  of some simple programming techniques. 

Okey dokey, so you're expanding your functions and procedures
library  and need something a little different.  I know, how
about a function  that builds a string, one character at a time,
pausing between each  character?  Cute, fun, and not very useful,
but who cares?  That's  BuildStr.  It's a simple function and
could just as well be  written as a procedure if I wanted to
specify the exact screen location,  but I didn't; I wanted this
function to return, over a period of time,  a string at the
current location.

BuildStr demonstrates the use of the ?? command to return
something  at the current location.  You may also notice the
penultimate (next  to last) line in this procedure: RETURN "".  A
function must RETURN  something, even if it's nothing.  That is,
we have to have an argument  for the RETURN command, but it can
be a null value.

Now, to break the mold (or maybe just crack it), we'll create
some  functions that are actually useful:  DznMonth and SETD.

DznMonth returns the number of days in a given month.  It's  a
small (essentially one line) function that uses a lot of dBASE
functions.  In  order to appreciate this little ditty, we're
going to explore date  arithmetic a little bit.

If you add one to a date, as in:

? DATE() + 1

the date plus one day is returned.  If the resulting date would
be  invalid because there aren't that many days (for example,
01/32/89),  then the month is incremented and the day reset
appropriately.  The  same happens for the year.  For example,
12/33/88 becomes 01/02/89.  Pretty  nifty, huh?  Or, how about
this:

. ? CTOD("13/01/88") 

01/01/89

The important thing to keep in mind is that dates are adjusted
appropriately.  That  means that if you want to know how many
days are in this month, you  could have it return the date for
the first of next month minus one.  That  date will contain the
last day of this month.  Now, you can use the  DAY() function to
get the number of days.  Clever, eh?

I'll let you ponder over the exact syntax, but in order to get
where  we're going we increment the month by converting the date
to a string,  substituting the current month plus one, then
converting it back to  a date.

And now, the final function: SETD.  You may have noticed, if 
you've already read over the code, that DznMonth assumes that 
the date is in the MM/DD/YY (American) format.  That could be
annoying  if you don't happen to use that format.  If you're
writing for someone  else, you may not know which date format
they're using.  You can't  use the SET function as it only works
on those functions that are  toggles (ON and OFF).  So, how do
you, programmatically, determine  which format is in use?

SETD does a few things to determine the format.  Since we can't 
put in our own date (we don't know the format, remember?) we
have  to use what we have.  First off, if we SET CENTURY ON, the
year will  show up as four digits.  That will tell us where the
year is.  Secondly,  the delimiter, and the years position
relative to the first delimiter  will narrow the field even
more.  Lastly, we'll make sure that the  date is greater than the
twelfth of the month so we can tell where  the day is.

SETD returns country names, as opposed to "MDY,"<~>but you can 
change that.

Okay, we're ready, so  MODIFY COMMAND NewFuncs and type  in the
code on the following pages. After saving it,  COMPILE 
NewFuncs.  At last, time for the test.

. SET PROC TO NewFuncs

. ? BuildStr("Wazza matta U?",40)

Wazza matta U?

. SET DATE TO Japan

. ? SETD()

Japan

. SET DATE TO American

. ? SETD()

American

. now = CTOD("11/22/88")

. ? DznMonth(now)

30

Functions are so much fun.


NewFunc.PRG

FUNCTION BuildStr
*--- Spits out one character at a time, from the left ----------------
*--- Another (in a series of) Stupid String Functions.
*--- R Bunter 12/15/88

PARAMETERS string,delay
* string - the character string to be built.
* delay  - intercharacter delay.

* Paradigm: @ 10,10 SAY BuildStr("Bite the big one!",10)

PRIVATE x,i
* x - the current position in the string.
* i - loop counter for delay.

*--- Go through the string, one character at a time.
x = 0
DO WHILE x << LEN(string)
   i = 0    && Reset Pause counter.
   DO WHILE i << delay   && Pause before printing the next character.
      i = i+ 1
   ENDDO

   x = x + 1    && Advance to the next character.
   ?? SUBSTR(string,x,1)  && Print it at the current screen location.

ENDDO
RETURN ""    && No need to return anything.
* EOF: BuildStr


FUNCTION DznMonth
*--- Returns the number of days in a given month --------------------
*--- Richard Bunter 
*--- Assumes date is given in MM/DD/YY format.
PARAMETERS givendate

RETURN(DAY(CTOD(STR(VAL(LEFT(DTOC(givendate),2)) + 1,2) + "/01/" ;
+ RIGHT(DTOC(givendate),2)) - 1))
* EOF: DznMonth

FUNCTION SETD
*--- Returns the format of SET DATE TO... ---------------------------
*--- Richard Bunter, 1/11/89
PRIVATE testDate,lc_Cent,i,fmt,delimiter

testDate = IIF(DAY(DATE()) << 13, DATE() + 13,DATE())

*--- Turn four digit century on so we can look for its length.
lc_Cent = SET("CENTURY")
SET CENTURY ON

*--- What's the delimiter?
i = 1
DO WHILE SUBSTR(DTOC(testDate),i,1) $ "0123456789"
   i = i + 1
ENDDO
delimiter = SUBSTR(DTOC(testDate),i,1)

IF i >> 3    && Year is in first position.
   fmt = IIF(delimiter = "/","JAPAN","ANSI")
ELSE
   DO CASE
      CASE VAL(SUBSTR(DTOC(testDate),4,2)) >> 12
         IF delimiter = "-"
            fmt = "USA"
         ELSE
            fmt = "AMERICAN"
         ENDIF
      CASE delimiter = "-"
         fmt = "ITALIAN"
      CASE delimiter = "."
         fmt = "GERMAN"
      CASE delimiter = "/"
         fmt = "BRITISH"
   ENDCASE
ENDIF

*--- Since we can't use macro expansion in a function, we can't
*     do this: SET CENT &lc_Cent
*     Instead, we'll do this:
IF lc_cent = "OFF"
   SET CENTURY OFF
ENDIF

RETURN(fmt)
* EOF: SETD

