Another Approach to Time Calculations

by Kevin Erskine

Published 1999-04-01    Printer-friendly version

NOTE: Clarion Magazine was not able to obtain an archive of the original source code for this article. If you have the source, we'd very much appreciate it if you would email it to us so we can post it for other readers.

Well the folks at ClarionOnline finally got me to write an article -- so be nice with any comments or reviews <g> -- its my first official entry into the article writing arena for the Clarion community.

During the last few weeks there have been several discussions on the correct way to calculate times spanning over several days. There was even an article in another online Clarion oriented magazine. After reading the newsgroups and the article I decided to write this article and give my perspective on the issue.

I found that other approaches to the problem did not go quite far enough with the solution or, in my opinion, did not provide what was actually needed.

The "Time Calculation" function discussed in this article will return the number of days, hours, and minutes for the range as well as a displayable string with the information. The sample program is written in C5EE-SR1 using the Clarion IDE. The first window displays credit and copyright information. The second window demonstrates the time calculation function.

 Window 1: Credits
Window 1: Credits

As stated above; you may copy and or modify this code anyway you like. The only restriction is that you may not take my code and use it as part of another 3rd-party add-on product for Clarion developers.

 Window 2: Function results
Window 2: Function results

The purpose of this function is

  1. to return the numbers of days, hours and minutes that have elapsed between a given set of date/time pairs
  2. to return this result as a decimal number which can be used on reports and handle summations correctly
  3. to return a formatted string of this information
  4. to return the above information, but as a cumulative amount from successive calls

Explanation of the code

typedecl.gif

First we define a data structure we will use to pass to our time calculation function. We are using the TYPE option on the GROUP structure. This TYPE structure will also be used in the prototype for our time calculation function. Place this definition in the Global | Embeds | After Global INCLUDEs of your application.

Clarion Definition

The TYPE attribute creates a type definition for a GROUP, QUEUE, or CLASS (a "named structure"). The label of the named structure can then be used as a data type to define other similar GROUPs, QUEUEs, or CLASSes (or you can use LIKE). TYPE may also be used to define named structures passed to functions, allowing the receiving function to directly address components of the type definition using Field Qualification syntax.

A GROUP, QUEUE, or CLASS declaration with the TYPE attribute is not allocated any memory. While the data members of a CLASS with the TYPE attribute are not allocated memory, the methods prototyped in the CLASS must be defined for use by any subsequent objects declared as that type. EXTERNAL and DLL are irrelevant.

Description of field use

The first four fields are passed into the function and specify the date \ time range we want to use in the time calculation.

On return; p_NumDays will contain the number of elapsed days, p_NumHrs will contain the number of elapsed hours and you guessed it, p_NumMins will contain the number of elapsed minutes. These three fields contain the actual elapsed time between the begin and the end parameters.

The p_DecHrsMins field contains the number of hours in the integer portion and the number of minutes in the fractional portion expressed in 1/60th of hour units. The reason for this is that on report detail lines, or other areas where you are summating the times you want the addition of the times to be correct.

p_TimeDisplay will contain the days-hours-minutes expressed in a "user-friendly" readable form.

The next five fields ending in "_Cum" will contain a cumulative total when called within a process loop for totaling purposes and behave in the same manner as their counterparts listed above.

p_ErrorMsg will contain a message indicating what the error was if the calculation could not be completed.

Our time calculation function has the following prototype;

SRC:CalculateElapsedTime FUNCTION(*t_TimeCalcVars p_TimeCalcVars),BYTE,PROC

p_TimeCalcVars is passed by address ( * ) so the function can modify the GROUPs data fields. The BYTE return contains the same value as GlobalResponse in case you want to use the function in an IF or WHILE type of construct. The PROC attribute allows us to code the function call outside of a conditional construct and not generate any compiler warnings.

locdata.gif

This is the local data area for function. Use of fields explained below.

calc_0.gif

Initialize all the return variables. GlobalResponse is set to RequestCancelled on the assumption of calculation failure.

calc_1.gif

I personally believe it is better to write "protective" code. If there is a possibility of invalid data being passed to a function, a little code will help protect your function from unwanted GPFs or invalid results. In this function, if any invalid data is passed, we just set the error message and return.

calc_2.gif

Using Clarion's ability to subtract dates using normal arithmetic, we first subtract the two dates and multiply by 24. This will gives us the number of days (hours) that have elapsed. If the result is 24, we need to check if we are within the same 24 hour period, but crossing two physical days. If we are then, just set the number of hours (p_NumHrs) to zero because it will be handled later.

calc_3.gif

Now, some people say I am a lazy programmer, but I personally believe that if you can make code more readable, do it. When you have to revisit it 3 to 6 months from now you will be able to figure out what you did and, more importantly, why.

For some reason TopSpeed has never addressed times like it has dates. In the date function arena they gave us DATE(month,day,year) to put a date together when you have the month, day, year. They also gave us the following functions to pull the date apart into its components; MONTH(date); DAY(date); YEAR(date). So, why did they drop the ball with times? Why not supply functions like; TIME(hours,minutes,seconds,hundreds);HOUR(time); MINUTE(time);SECOND(time); HUNDREDS(time), would it have killed the compiler designers?

By using the FORMAT function we can easily access the time components without resorting to sometimes-cryptic long algebraic expressions involving that magic number of 8,640,000. To calculate p_NumMins we convert each time value into minutes and subtract to two. If the number of seconds for either time is greater than or equal to 30 seconds we round up to the next minute. (This was a judgement call for my needs. If you do not want this, just remove the CHOOSE part of the equation.)

calc_4.gif

I use reference variables to adjust the time calculation. Since We are not only returning elapsed calculated for the current call, but are also cumulating for successive calls I did not want to repeat the same code twice. I could have created a procedure and passed the p_TimeCalcVars structure, but I decided to use a local ROUTINE and use reference variables. See below for further discussion on the ROUTINEs.

calc_5.gif

Now we add this set of calculations to the cumulative variables and call the same ROUTINEs to adjust and round the values.

calc_6.gif

Computations complete. Set GlobalResponse and return to the calling procedure.

adjtimes.gif

We need to do several things at this point to the calculated p_NumMins field. First, if the p_EndTime was less than the p_BgnTime we will end up with a negative number. We need to add 1400 minutes to the value to wrap it around 24 hours.Now if we have more than 60 minutes we roll it up into the p_NumHrs field and subtract from the p_NumMins field. We then do the same thing for p_NumDays and p_NumHrs.

Now that we have our calculated values we can now construct a decimal equivalent of our time. This time field is what should be used on reports if you are displaying elapsed time and summing them in a control break. We make the minutes a fraction of an hour by dividing by 60. By doing this, it makes 15 minutes .25 (1/4) of an hour.

The need to display elapsed time in an end-user readable format was required. There is more code above than is actually needed, but we wanted to make sure the English text read correctly, especially in singular and plural times. The code is pretty straightforward.

Well, that about does it. I hope this discussion of elapsed time calculations has cleared up any outstanding misconceptions or omissions in previous time calculation discussions.

Printer-friendly version