SIMPLE DATE
HANDLING ROUTINES



Introduction

This annex provides the BASIC code for simple date-handling routines which can be used in conjunction with programs for credit calculations.

These are written in the syntax of BBC BASIC version 2 and have been retained in that form in later programs using (backwards compatible) BASIC V.  They are presented here as a simple example program which calculates the user's age from their date of birth and the current date.

They do not provide a complete solution for credit calculations, only the straightforward translation of a date in dd,mm,yyyy format to an integer day value, and the translation of that integer value back to dd,mm,yyyy.  They do however provide the basic tools needed to extend their functions - including an array of the number of days in each month and a function to detect leap years and the provision of results in a convenient form for output to screen, file or printer.

The date values used are based on a value of 1 for the 1st of January 1600 and the routines can only be regarded as valid for dates on or after that.  Because of the limitations of the way date values have been stored, in BBC BASIC the routines also cannot handle values greater than 2,147,483,647 which represents 10th July 5,881,210.  If you want to use the routines for historical calculations, you may want to note that current Gregorian calendar was introduced at different times across the Europe - eg not until 1752 in England.

I should make it clear that the kernel of these routines, the date to date value conversion and its associated routines, is not my work.  Regrettably I cannot provide a credit for the original author, but I believe I first obtained it from a now defunct magazine called 'Computing Today', probably in the early 1980s, and have converted it to BBC BASIC and altered it a number of times since.

The Program Listing

       10 REM Example Date-handling routines.
       20 REM BBC BASIC 2, adapted by Brian Stewart.
       30 REM NB: dates from 1600 onwards.
       40 :
       50 PROCinitdates
       60 :
       70 CLS
       80 PRINT "CALCULATE YOUR AGE"
       90 INPUT'" Please enter your DOB (d,m,yyyy): " bd%,bm%,by%
      100 INPUT "Now enter today's date (d,m,yyyy): " td%,tm%,ty%
      110 :
      120 PROCday(bd%,bm%,by%):dob%=DATE%
      130 PROCday(td%,tm%,ty%):tod%=DATE%
      140 age%=tod%-dob%
      150 :
      160 PROCdate(dob%):PRINT'"  DOB: ";DATE$;
      170 PRINT " (Day=";DAY%;" Month=";MONTH%;" Year=";YEAR%;")"
      180 PROCdate(tod%):PRINT "Today: ";DATE$;
      190 PRINT " (Day=";DAY%;" Month=";MONTH%;" Year=";YEAR%;")"
      200 PRINT'"You are:"
      210 PRINT age%;" days old,"
      220 PRINT (age%)/7;" weeks old,"
      230 PRINT 12/365.25*age%; " months old, and"
      240 PRINT age%/365.25;" years old."'
      250 END
      260 :
      270 REM --- simple date handling routines ---
      280 DEF PROCinitdates
      290 DIM dayin%(13),daytot%(13)
      300 FOR n%=1 TO 13:READ dayin%(n%),daytot%(n%):NEXT
      310 ENDPROC
      320 :
      330 DATA 31,0,28,31,31,59,30,90,31,120,30,151,31,181
      340 DATA 31,212,30,243,31,273,30,304,31,334,0,365
      350 :
      360 DEF PROCdate(n%)
      370 LOCAL y%,m%,dr%,d%,ly%
      380 y%=INT(n%/365.25)+1600
      390 REPEAT dr%=n%-FNyear(y%):ly%=FNleap(y%)
      400   IF dr%>365+ly% y%=y%+1
      410   IF dr%<1 y%=y%-1
      420 UNTIL dr%>=1 AND dr%<=365+ly%
      430 m%=0
      440 REPEAT m%=m%+1
      450   d%=daytot%(m%+1):IF ly% AND m%>1 d%=d%+1
      460 UNTIL dr%<=d%
      470 d%=dr%-daytot%(m%):IF m%>2 d%=d%-ly%
      480 PROCrecdate:ENDPROC
      490 :
      500 DEF PROCday(d%,m%,y%) LOCAL n%
      510 n%=FNyear(y%)+daytot%(m%)+d%-FNleap(y%)*(m%>2)
      520 PROCrecdate:ENDPROC
      530 :
      540 DEF FNyear(y)
      550 =365*(y-1600)+INT(ABS(y-1601)/4)-INT(ABS(y-1601)/100)+INT((y-1201)/400)
      560 :
      570 DEF FNleap(y%) LOCAL l%
      580 IF y% MOD 4=0 AND y% MOD 100<>0 l%=1
      590 IF y% MOD 400=0 l%=1
      600 =l%
      610 :
      620 DEF PROCrecdate
      630 DATE%=n%:DAY%=d%:MONTH%=m%:YEAR%=y%
      640 DATE$=FNpd(d%)+"/"+FNpd(m%)+"/"+STR$(y%)
      650 ENDPROC
      660 :
      670 DEF FNpd(x%) IF x%<10="0"+STR$(x%) ELSE =STR$(x%)
    

The Date Handling Routines in Detail

The routines themselves start at line 260.

The procedure PROCinitdates must be run before using the conversion routines.  It sets up two integer arrays, both 13 elements in size, and copies values into them from the following DATA statements.  The DATA statements contain information on the number of days in each month interlaced with cumulative total to the number of days in a year to the start of each month (zero for January, 31 for February, and so on).  February is assumed to have 28 days in these values.  If you require more sophisticated routines, this would also be the point to set up, eg arrays of the names of the months and days.

The procedure PROCdate() converts a date value to a date in dd,mm,yyyy format.  It relies on two other functions to do this:  FNyear() which calculates the number of days from 1/1/1600 to the end of the year prior to the one passed to it, and FNleap() which determines whether the year passed to it is a leap year, using the full 4-year, 100-year, 400-year rule - not just by dividing by four, and returns 1 if it is or zero otherwise.

The procedure PROCday() converts the three integer values dd,mm,yyyy passed to it to a date value - ie it does the opposite of PROCdate().  It is fairly straightforward, simply adding the value returned by FNyear() and the value in the array daysin() to the day value, however the end of line 510 may need some explanation because it includes a boolean calculation in the form '*(m%>2)'.  The expression in brackets is evaluated by BBC BASIC as either TRUE (a value of -1) if m% is greater than 2 or FALSE (a value of zero) if it isn't.  The result is multiplied by -FNleap() - so the effect is to add one to the total if the year is a leap year and the month is later than February and nothing otherwise.

PROCdate() and PROCday() both call PROCrecdate before ending.  The conversion routines do not return values, instead they set the values of global variables and PROCrecdate ensures that they are all reconciled.  The variable used are:

     DATE%  for the date value of the last converted date
     DATE$  for a string value of the last date converted (format 'dd/mm/yyyy')
      DAY%  for the day in the month, 1 to 31
    MONTH%  for the month, January=1, etc
     YEAR%  for the year.
    

This would also be a good place to add any enhancements, like a fuller date format, eg 'Friday 17th March 2000' - you might like to note that the following function will convert a date value to a day of the week, coded as Sunday=1, Monday=2 ... Saturday=6:

    DEF FNdow(d%)=((d%+5) MOD 7)+1
    

(adjusting the value '5' will alter the first day of the week)

PROCrecdate also uses FNpd() to ensure that single digit days and months in DATE$ are padded with leading zeroes.  You can omit this function and the calls to it, or replace the zeroes with spaces, if you prefer.

The Example Program

Hopefully this is fairly self-explanatory in demonstrating the routines in use.

PROCinitdate is run and the user is prompted to enter two dates.  These are passed to PROCday() in lines 120 and 130 and the resulting values of DATE% stored in dob% and tod%.  Line 140 calculates age% as the difference between the two dates (in days).

Lines 160-190 demonstrate the opposite conversion.  The variables dob% and tod% are passed to PROCdate() and the resulting values for DATE$, DAY%, MONTH% and YEAR% printed out.

Finally the program does some simple calculations on age% to provide the user's age in various formats.




Brian Stewart.
February 2004