Cycle Calendar

This useful program will compute cycle dates between a starting date and an ending date.  The task is really quite simple because of the TDateTime variable type in which ESPL stores date and time values.  The integral part of a TDateTime value is the number of days that have passed since 12/30/1899.  The fractional part of a TDateTime value is fraction of a 24 hour day that has elapsed. 

To find the fractional number of days between two dates, simply subtract the two values.  Likewise, to increment a date and time value by a certain fractional number of days, simply add the fractional number to the date and time value.

The program can be broken down into these 5 sections, which are color coded for identification purposes:

  • Associate execution with an ESPL button
  • Declare variables
  • Input period, starting and ending dates
  • Convert inputs to working variables
  • Generate the cycle dates and print them
procedure CycleCalendar;
var 
  s: string;
  d1,d2: TDateTime;
  i,period: integer;
begin
  s:='30 '+DateToStr(Now)+' '+DateToStr(Now+366);
  s:=InputBox('Cycle Calendar','Enter period and two dates separated by a space',s);
  if length(s)>0 then begin

   
    period:=StrToInt(GetToken(1,s));
    d1:=StrToDate(GetToken(2,s));
    d2:=StrToDate(GetToken(3,s));
    i:=0;

    
Output(eClear);
    while d1<=d2 do begin
      writeln(Align(i,3),' - ',FormatDateTime('dd mmm yyyy dddd',d1));
      inc(i); d1:=d1+period;
    end; 
 
end;
end;

{----- MAIN PROGRAM -----}
begin
  if Who=9 then CycleCalendar;
end;

A detailed explanation of the program's construction follows.

  • Associate execution with an ESPL button

{----- MAIN PROGRAM -----}
begin
  if Who=9 then CycleCalendar;
end;

All ESPL programs have a Main Program section that consists of a begin..end block, with various statements between them.   We want the program to generate calendar dates when one of the buttons on the ESPL editor form is clicked.  There are 10 buttons that could be used.   Clicking on the Run button, or any of the buttons labeled 1 through 9 will execute the program.   But how does the program know which button should print the calendar, as opposed to some other task?

ESPL was designed so that when a button is clicked, a unique value is assigned to a global variable called Who.  The Run button will assign a value of zero, and buttons 1 through 9 will assign values 1 through 9 respectively.  In this example, the calendar function is arbitrarily assigned to respond to button 9 by testing to see if the Who variable has a value of 9.  If it does, then we know that button 9 was clicked, and the CycleCalendar procedure is called.

  • Declare variables

procedure CycleCalendar;
var 
  s: string;
  d1,d2: TDateTime;
  i,period: integer;
begin
  {other statements go here}
end;

All procedures and functions must precede their being used.   This is why the CycleCalendar procedure code is positioned above the Main Program block.

The procedure consists of a var section where all variables that are needed in the procedure are declared.   This example needs a string variable, two TDateTime variables to hold the beginning and ending dates, and two integer variables.  The procedure body consists of a begin..end block with various statements between them.

  • Input period, starting and ending dates

    s:='30 '+DateToStr(Now)+' '+DateToStr(Now+366);
    s:=InputBox('Cycle Calendar','Enter period and two dates separated by a space',s);
    if length(s)>0 then begin  
      {other statements go here}
    end;

The InputBox function is used to obtain from the user three items, the number of days in the cycle period, the starting date and the ending date.   One approach could have been to use three input boxes to obtain these items.  My approach was to have all three items entered in one input box, and then parse the entry to find each item.

The proper format for the entry of the three items is to enter the period, a space, the beginning date, a space, and then the ending date.  The space character is used as a delimiter between each item.   The dates are entered in the format of mm/dd/yyyy.  The date string must consist of two or three numbers, separated by the character defined by the DateSeparator global variable.  The order for month, day, and year is determined by the ShortDateFormat global variable--possible combinations are m/d/y, d/m/y, and y/m/d.  If the string contains only two numbers, it is interpreted as a date (m/d or d/m) in the current year. Year values between 0 and 99 are assumed to be in the current century.  The DateSeparator and ShortDateFormat global variables are set in your Windows operating system.

The InputBox function takes three parameters.   The first is a string used as the header for the Input form.  The 2nd string is a prompt placed above the input line.  The prompt reminds the user to enter three items separated by a space.   The 3rd string is a default input.  I chose to provide one consisting of a 30 day period, starting Now and ending 1 year later.   This default is arbitrary and not needed.  It does illustrate how a default could be created for a frequently used cycle.

The InputBox function returns the users entry as a string, which we place in our string variable.   We check the length of the returned answer to be sure it is not an empty string.

  • Convert inputs to working variables
    period:=StrToInt(GetToken(1,s));
    d1:=StrToDate(GetToken(2,s));
    d2:=StrToDate(GetToken(3,s));
    i:=0;

Now we need to parse the input string into the three items:  period, starting date, and ending date.  The GetToken function comes in handy for this task.  All we have to do it pass the function the token position and the string to extract from.  By default GetToken looks for a space character as the delimiter that separates the tokens in the string it is parsing.

GetToken(1,s) returns the 1st item, the cycle period, but as a string.  We want a value, not a string, so the StrToInt function is used to convert the string to an integer value and assign the value to our period variable.

GetToken(2,s) returns the starting date as a string.   This string is converted to a TDateTime variable using the StrToDate function.

The integer variable i will count our cycles, so it is initialized to zero before we begin our loop.

  • Generate the cycle dates and print them

    Output(eClear);
    while d1<=d2 do begin
      writeln(Align(i,3),' - ',FormatDateTime('dd mmm yyyy dddd',d1));
      inc(i); d1:=d1+period;
    end; 

Actually, the hard part is done.  All we need to do now is loop from the beginning date through the ending date and print the cycles in the Output window.  So, the output window is cleared.

The while statement is used to control our loop.   The date D1 is compared to the date D2, and the loop continues as long as D1 is less than or equal to D2.   D1 is initialized as the starting date, and D2 has the ending date.p

The writeln statement prints text in the output window.   The cycle counter is aligned to print in three columns.   Then a separator string of space, dash, space is printed, followed by the cycle date.   The date is formatted using the powerful FormatDateTime function, which takes the date in D1 and returns the day, month, year and name of the day.

After printing a cycle date, the cycle counter is incremented, and the working date D1 is moved to the next date by adding the number of days in the cycle period.  This is the beauty of a TDateTime variable.  We can advance to the next date by simply adding the number of days in the period.   We do not have to worry about leap years, or the number of days in a month, which keeps a complex task really simple.

Well that's it.  I hope you enjoy this useful program and understand how it was designed.   Please read the Help | ESPL Contents documentation in Ensign Windows for added understanding about any of the statements that were used.