Chapter 10: Standard Data Types

Understanding Data Types

All computer systems store and use information which can be understood at a "human level" in various distinct ways. For example, this sentence comprises up of a series of individual letters, or alphabetic characters, that are combined into words which, in turn, are organised into sentences. I started writing the first draft of this chapter on 18/11/2015 and it took roughly 20 minutes to draft this introduction.

Computer programming languages are able to classify certain types of information, or data, in a variety of distinct ways and these are very useful when your program needs to use such information. For example, the words used in this sentence can be classed as a series of characters (or a string of characters in programming terminology) and, therefore, the sentence can be represented in a computer program as "character" data. The 18/11/2015, or even 18th November 2015, can be classified as "date" data whilst 20 (minutes) can be classified as "numeric" data. Harbour not only uses these (and other data types) when a program runs but it can also organise information into various data types and store them for longer term use in a database file (there are some exceptions to this and I will explicitly state when this is the case and explain why).

The rest of this chapter focuses primarily on exploring each of the standard data types that Harbour supports. I will also introduce ways to use (or manipulate) each data type in a program using some of the more common Harbour commands and functions including how to convert from one data type to another. After very careful consideration, I have decided not to include detailed examples of when and why you might use each of the commands and functions explored. Many of these will be used later on in ePortfolio and it will be easier to understand them then in the context of a more complete application.

CHARACTER

The most obvious place to start is with the CHARACTER data type! In fact, you should already have a good understanding of this data type as we have used them extensively in section 1.

The character data type is the least restrictive of all the data types and it is, therefore, very flexible. You may be surprised to hear that Harbour recognises other types of character other than the usual letters in the alphabet. Numbers, punctuation marks, foreign language symbols (for example, "ß") and a range of special symbols (for example, "®") can also be represented as a character although you are likely to use letters and numbers the majority of the time.

Creating and using character data is very straightforward and you have already had hands-on practice in both the creation and use of these throughout section 1. As a brief reminder, LOCAL cLabel := "Hello" creates a local memory variable which contains the character string Hello (remember that the inverted commas delimit a character string and are not actually part of the character string. Similarly, using CAPTION "Hello" as part of a BUTTON would display Hello on the button (again, without the inverted commas).

Under certain circumstances, you will also need to use, change or otherwise manipulate character strings in your programs. The following Harbour commands and functions are the most common ways to do this.

Joining CHARACTER Data Together

On occasions you will need to create a single character string from two or more other character strings by using the + operator to "join", or concatenate, them together. I cover mathematical operators, including concatenation, in greater detail in chapter 12.

cFullName := "John" + " " + "Smith"

In the above example, three separate character strings, "John" a single space and "Smith" are concatenated and stored as "John Smith" in a memory variable called cFullName.

This type of concatenation is useful when you capture information from a user in separate parts of your program. For example, you may wish to capture and store a customer's first and last names separately but use them later on to display their full name.

Removing Spaces with ALLTRIM(), LTRIM() and RTRIM()

The ALLTRIM() function removes any spaces at the beginning and end of a character string. In certain circumstances, you might only want to remove spaces at the beginning or end of a character string. The LTRIM() function will only remove spaces from the start (or leading) side of a character string whilst RTRIM() only removes any spaces from the end (or trailing) end of a string.

As previously mentioned, most functions expect certain information to be included between the two brackets. Where you include (or express) such information, these are referred to as an argument and the actual information is the value of the argument.

In the case of these three functions, only one argument is required which must have a value which is has a data type of "character". All three functions also return a value which can be used by other parts of the program and that returned value is also a character data type.

cTrimmed := ALLTRIM(" Lots of spaces before and after this line ")

In the above example, the leading and trailing spaces are removed and the result, "Lots of spaces before and after this line", is stored in a memory variable called cTrimmed.

Inserting Characters with PAD(), PADC(), PADL() and PADR()

The PAD() function increases the length of a character string by adding additional characters to the end of the character string up to the specified length.

cPaddedStr := PAD("Hello", 10, "!")

In above example, additional exclamation marks (or any other character you specify) will be added to "Hello" until the character string is 10 characters in length (or any other length you specify). The result, "Hello!!!!!", will then be stored in a memory variable called cPaddedStr. An identical function, PADR(), works in exactly the same way was as PAD() by adding additional characters to the end of the character string.

In certain circumstances, you might want to insert additional characters to the start of a character string up to a specified length and the function PADL() can be used to do this. The PADC() function works in a similar way by increasing a character string to a specified length by inserting a specified character but it does this by inserting the additional characters by inserting them in the middle, or centre, of the character string.

Convert to UPPER() and LOWER() Case

cUpperStr := UPPER("a lower case character string")

The UPPER() function converts a character string to consist of entirely uppercase letters. In the above example, the result, "A LOWER CASE CHARACTER STRING" would be stored in a memory variable called cUpperStr.

Where you need to do the opposite and convert a character string to consist of entirely lowercase letters, the LOWER() function can be used.

Extracting Sub-strings with LEFT(), RIGHT() and SUBSTR()

cLeftStr := LEFT("ABCDEF", 3)

The LEFT() function is used to copy a specified number of characters – a "sub-string" – from the left (or start) of a character string. In the above example, the first 3 characters (or any other length you specify) in "ABCDEF" are "extracted" and stored in a memory variable called cLeftStr (the result would be "ABC").

A related function, RIGHT() extracts a specified number of letters as a sub-string from the right (or end) of the character string. For example, RIGHT("ABCDEF", 3) would return "DEF".

Another related function, SUBSTR(), is used to extract a "sub-string" from a specified position and to a specified length.

cSubString := SUBSTR( "This is a line of text", 6, 2 )

In the above example, I want to extract a sub-string that is 2 characters long from the 6th letter onwards. The result, "is", will be "extracted" and stored in a memory variable called cSubString.

Transform a String with STRTRAN()

cNewString := STRTRAN("This is a line of text. It is a nice line of text", "line", "string")

The string transformation function, STRTRAN(), is used to transform a character string from one form to another by looking for all occurrences of a word or phrase which is then changed to a different word or phrase.

In the above example, the character string "This is a line of text. It is a nice line of text" is searched for any occurrence of the word "line" which is then changed to "string".

The result, "This is a string of text. It is a nice string of text" is then stored in a memory variable called cNewString.

Transform a String with STUFF()

cNewString := STUFF("This is a line of text", 7, 0, " much longer")

The STUFF() function is used to insert an additional word or phrase into a character string at a specified position. In the above example, the phrase "much longer" is inserted into the character string "This is a line of text" from the 7th character from the start of the string. The result, "This is much longer a line of text", is then stored in a memory variable called cNewString.

cNewString := STUFF("This is a short line of text", 10, 5, "much, much longer")

The STUFF() function can also be used to delete a specified number of characters from the character string before it inserts a new word or phrase. In the above example, the string "This is a short line of text" has 5 characters deleted from the 10th character onwards (in this example, the word "short" is removed). The phrase "much, much longer" is then inserted from the same position and the result, "This is a much, much longer line of text", is then stored in cNewString.

Search a Character String with AT() and RAT()

nPosition := AT("Oh, my love is like a red, red rose", "red")

The AT() function is used to find the first occurrence of a sub-string in a character string starting the left of the character string. It then returns a numeric value equal to that position. In the above example, the character string "Oh, my love is like a red, red rose" is searched (or parsed as it is often referred to in programming terminology) for the first occurrence of the word "red" and it's numerical position, 23, is returned and stored in the memory variable nPosition.

A related function, RAT() can be used to return the position of the last occurrence of a sub-string within a character string by searching the character string from the end (or right side) of the character string. In the above example, if RAT() was used, the result would be 28.

A number of the functions described above have been included in an example program file for you to try. You can download a copy of the source code file from here - CharManip.PRG

NUMERIC

The second data type to explore is the NUMERIC data type. As with the CHARACTER data type, you should already have a good understanding of these as we have used them extensively in section 1.

Obviously, this data type is used to represent numbers, including decimal places, and it is, therefore, intrinsically a more accurate and more restricted data type. Equally obviously, this is the data type on which mathematical operations are performed (or executed) in computer programs.

Creating and using numerical data is also very straightforward and you have already had hands-on practice with these in section 1. As a brief reminder, LOCAL nRow := 32 creates a local memory variable, nRow, which contains the numeric value of 32. Also notice that there are no inverted commas surrounding the value? If you did use inverted commas ("32") the program would recognise this as a character string which would cause problems later on if you tried to execute a mathematical operation on it!

There are multiple ways to manipulate numeric data and the following explores the most common Harbour commands and functions you are likely to use.

Add, Subtract, Multiply and Divide

Numeric data can be manipulated by using mathematical operators which are covered in more detail in chapter 12.

Convert from CHARACTER to NUMERIC with VAL()

nNumber := VAL("99.99"))

The VAL() function converts a character string into a numeric data value and is most commonly used when a character string that only contains numbers, including decimal places, needs to be manipulated mathematically. In the above example, the character string "99.99" is converted into a numeric value of 99.99 which is then stored in the nNumber variable.

Remove Decimal Places with INT()

nInteger := INT(99.99)

The INT() function is used to remove all decimal places from a numeric value by simply removing, or truncating, the decimal value from the number. It is important to note that the decimals are simply removed without any rounding up or down. In the above example, the numeric value 99.99 would be converted to numeric value of 99 which is then stored in the variable nInteger.

Rounding Up or Down With ROUND()

nRounded := ROUND(99.99, 0)

The ROUND() function is used to round a numeric value up or down to a specified number of decimal places. In the above example, the numeric value required needs to remove all decimal places (as specified by the 0). In this case the function looks at, or evaluates, the full decimal value .99 which is above the value it needs – 5  –  in order to round up. The result, 100 , is then stored in the variable nRounded.

nRounded := ROUND(99.84, 1)

In this example, the ROUND() function needs to return a numeric value containing 1 decimal place. It evaluates the portion of the decimal value that needs to be removed, "4" and assesses that it is below the value – 5 – needed to round up the previous decimal place "8". It therefore simply removes the second decimal place and returns a value of 99.8 to be stored in the variable nRounded.

Finding a Minimum or Maximum Value with MIN() and MAX()

nLower := MIN(10, 20)

The MIN() function compares two numeric values, including any decimal places, to find which of them contains the lower value. In the above example, the function evaluates the two numeric values 10 and 20 and identifies that 10 is the lower of the two which is then stored in the variable nLower.

nHigher := MAX(100.1, 100.2)

The related function, MAX(), compares two numeric values, including any decimal places, to find which of them contains the higher value. In the above example, the function evaluates the two numeric values 100.1 and 100.2 and identifies that 100.2 is the higher of the two which is then stored in the variable nHigher.

These two functions can also be used when comparing two dates that are created with the DATE data type.

Convert from NUMERIC to CHARACTER with STR()

cNumber := STR(99.99, 5, 2)

The STR() function is used to convert a numeric value to a character string and is most commonly used where a number needs to be displayed on screen or printed. In the above example, the number 99.99 is converted into a character string that is 5 characters long including 2 decimal places and the actual decimal place character. The result "99.99" is then stored in the memory variable cNumber.

Find the Length of a Character String with LEN()

nCount := LEN("This is a line of text")

The LEN() function counts the number of characters in a character string (excluding the inverted commas!) and returns a numeric value. In the above example, the characters in the character string "This is a line of text" are counted and the result, 22, is stored in the variable nCount.

A number of the functions described above have been included in an example program file for you to try. You can download a copy of the source code file from here - NumericManip.PRG

LOGICAL

The LOGICAL data type is also one that you have some understanding of from the later chapters in section 1. They are probably the most straightforward to understand as they only have two possible values, true or false which is expressed by using .T. to represent true or .F. to represent false.

Creating a logical data value to store in a variable is very straightforward as you found in section 1. As a brief reminder, LOCAL lUsrresp := .T. creates a local memory variable, lUsrresp, which contains the logical value of .T. (true).

However, there are also multiple other ways to work with logical data and they are mainly used when you need to compare two values of any data type in order to execute one of two different actions depending on the result of the comparison. The result of any such comparisons creates a logical condition which enables what is commonly referred to as logic based operation or logical execution.

To understand how logical execution works in a little more detail, we will explore some of the more straightforward Harbour commands and functions that rely on a logical condition. I will introduce some of the more complex, but powerful and flexible, ways to manage your programs with logical conditions and logical execution in later parts of this guide.

Comparing Values with the IF() Function

The IF() function performs a quick and simple comparison between two data values to create a logical condition that is used to determine which action to execute next. It expects three arguments; a logical expression to evaluate, and two arguments that express the actions to execute in the event of either a true or false condition. The function returns a value that is the result of whichever of the two actions are executed.

lCompareVal := IF(1 == 1, .T., .F. )

In the above (simplistic) example, the IF() function evaluates the first argument to see if 1 is exactly equal to 1. For now, all you need to know is that using two equals' signs means exactly equal; we will cover this, and other relational operators, in further detail in chapter 14. Obviously, in this simple example, the resulting logical condition will always be true and, that being the case, the function simply returns a value of .T. which is then stored in the memory variable lCompareVal.

xVal := IF(1 <> 1, DoThisFunction(), AnotherFunction() )

In this example, the IF() function evaluates the first argument to see if 1 is not equal to 1. Obviously, the resulting logical condition will always be false but this time it executes a User Defined Function (UDF) called AnotherFunction() which is the specified action for it to take when the logical condition is false. Once this UDF completes the value returned by it is then stored in the memory variable xVal.

You may notice that this single line of code does not tell us what value to expect from either the DoThisFunction() or the AnotherFunction() function and this introduces the idea that the IF() function can return a value of any data type. Whilst that may sound confusing at first, it is actually incredibly useful as you will see in later sections of this guide.

IF(cString1 == cString2, DoTrueUDF(), DoFalseUDF() )

In this example, IF() compares the values stored in two memory variables, which must already exist, called cString1 and cString2 to see if those values are exactly equal to one another. When this is the case, the DoTrueUDF() is executed but if the two values are not exactly equally to one another, the DoFalseUDF() is executed instead. You will notice that this example does not "do anything" with the values that would be returned by either of these UDFs. This is completely valid and shows that you do not always have to "do something" once IF() finishes running.

IF(lAskUser(), DoTrueUDF(), DoFalseUDF() )

In this final example, IF()  does not do any kind of comparison. Instead, it executes a UDF called lAskUser(), which must exist, and evaluates the value it returns which must be a logical value for IF() to work orrectly. When the returned value is true, the DoTrueUDF()  is executed and if the returned value is false DoFalseUDF() is executed instead.

One of the most common places where you might use the IF() function is when you need to compare two data values to one another and execute an action depending on the outcome.

Finally, there is a function called IIF() which is exactly the same as IF(). This "duplication" was deliberately included by the developers of Harbour to ensure that any application previously written in any legacy programming language, such as Clipper, would continue to work. It makes no difference which of these two you prefer to use and, indeed, you can even use both at any point in your computer code.

DATE

As you might expect, the DATE data type is used to work with calendar dates. However, whilst the date data type initially sounds extremely straightforward, there are several aspects to them that you need to be aware of when working with them which make them slightly more complicated than they first appear! This is because of the way Harbour deals with the way different parts of the world use dates (mainly, the format of a date) and how Harbour then interprets information to be used as a date, especially when a user provides these as input into the program.

To start understanding dates, we will explore some of the Harbour commands and functions that can be used to manage and manipulate dates.

Setting the DATE Format

SET DATE <cDateFormat>

This command is used to set the date to a style or format that your users would be most comfortable with and it is most usually run at program launch (or initialisation). It is not mandatory to set a date format as Harbour is designed to use American style dates by default. The following list details all of the different date formats that are available where dd represents the numeric day of the month, mm represents the numeric month and yy represents the year numerically. Each part of the date is separated by a character such as a forward slash;

Date Formats
SET DATE BRITISH this command sets your program to use the British style of date where the day of the month is expressed first, followed by the month and ending with the year with each separated by a back slash (dd\mm\yy). For example, 01\03\16 equates to 1 March 2016.
SET DATE AMERICAN this command sets the date to an American format which is expressed as mm/dd/yy
SET DATE USA this command also sets the date to an American format which is expressed as mm/dd/yy
SET DATE FRENCH this command sets the date to a French format which is expressed as dd/mm/yy
SET DATE GERMAN this command sets the date to a German format which is expressed as dd.mm.yy
SET DATE ITALIAN this command sets the date to a Italian format which is expressed as dd-mm-yy
SET DATE JAPAN this command sets the date to a Japanese format which is expressed as yy/mm/dd
SET DATE mm:dd:yy this command enables you to express a customised date format by passing a value to represent the desired format. In this example, mm:dd:yy, sets the date to express the month first, then the day, then the year with each being separated by a colon

 

Displaying the CENTURY Digits

By default, Harbour manages dates without displaying any digits to represent the century. In the majority of cases, this will not cause any issues to the expected operation of your program but, from time to time, you may want to be absolutely sure or, more usually, you may want to actually display the century digits using some of the functions that follow especially if you are working with very old dates.

SET DATE BRITISH
SET CENTURY ON

In the above example, your program will first set the date format to British and then instruct the program to display the digits to represent the century. This has the effect if changing the date format from dd\mm\yy to dd\mm\yyyy.

SET DATE BRITISH
SET CENTURY OFF

By contrast, the above example will first set the date format to British and then instruct the program to not display the digits to represent the century (the default setting). Thus, the date format will remain as dd\mm\yy.

Getting the System Date with DATE()

Creating a date data value and storing storing the current date in a variable is fairly straightforward.

LOCAL dDateNow := DATE()

The above example will create a local memory variable, dDateNow, which contains what the current date whenever the program is run (according to the computers date setting).

Adding or Subtracting Date Values

You can change – or manipulate – date values using simple mathematical operators.

dDateNow := dDateNow + 7

In the above example, the date previously stored in dDateNow is changed by adding 7 days to whatever was previously stored in the variable. Dates can also be "reduced" using the minus operator. We will explore the use of mathematical operators in greater detail in Chapter 13.

Convert a Date with DTOC()

cDateNow := DTOC( DATE() )

The DTOC() function converts a date into a character string (think of the function name as meaning "date to character"). In the above example, the memory variable cDateNow would contain the current date as a character string. An example of where this function is useful is when a date stored as a DATE data type needs to be converted into a CHARACTER data type to display on the screen.

Convert to a Date with CTOD()

dDate := CTOD("18/11/2015")

This function is the opposite of the one above in that it converts a CHARACTER string into a DATE data type. This function is useful, for example, when a user has entered a date on screen in character form and you need to convert it to a proper date to store in a database file.

Day Values and the DAY() and CDOW() Functions

nDayNumber := DAY( DATE() )

The DAY() function extracts the day from a date and returns a numeric value between 1 and 31 which corresponds to the day of the month. In the above example, nDayNumber will contain a number that equals the day or the month whenever the function is executed. So if the date was the 15th of December, nDayNumber would contain a numeric value of 15.

cDayName := CDOW( DATE() )

This function returns a character string to represent the name of a particular day. In the above example, CDOW() will return the name of the day whenever  the function is run. So if the date was the 15th of December 2015, cDayName would contain the character value "Tuesday".

Month Values and the MONTH() and CMONTH() Functions

nMonthNumber := MONTH( DATE() )

The MONTH() function extracts the month from a date and returns a numeric value between 1 and 12 which corresponds to the number of the month. In the above example, nMonthNumber will contain a number that equals the month whenever the function is executed. So if the date was the 15th of December, nMonthNumber would contain a numeric value of 12.

cMonthName := CMONTH( DATE() )

This function returns a character string to represent the name of a particular month. In the above example, CMONTH() will return the name of the month whenever the function is run. So if the date was the 15th of December, cMonthName would contain the character value "December".

Year Values and the Year() Function

nYearNumber := YEAR( DATE() )

The YEAR() function extracts the year from a date and returns a numeric value which corresponds to the year. In the above example, nYearNumber will contain a number that equals the year whenever the function is executed. So if the date was the 15th of December 2015, nYearNumber would contain a numeric value of 2015.

Comparing Dates with MIN() and MAX()

Two dates can also be compared to each other to find out which one is "older" or "newer" than the other.

dOlderdate := MIN( DATE(), DATE() + 30 )

In the above example, the current date will be compared to the current date plus 30 days. Obviously, the result will be the current date and that will then be stored, as a DATE data type, to dOlderdate.

dNewerdate := MAX( DATE(), DATE() + 30 )

In the above example, the opposite happens in that the current date will be compared to the current date plus 30 days. Obviously, the result will be the current date plus 30 days and that will then be stored, as a DATE data type, to dNewerdate.

A number of the functions described above have been included in an example program file for you to try. You can download a copy of the source code file from here - DateManip.PRG

Nil

The NIL data type (also sometimes referred to as a NUL data type) is sometimes a little confusing to the beginner but it is a valid data type and in certain circumstances and with certain functions, you can actually work with these! The most likely place where you might do this is if you have written your own function that would usually return a value of some sort but, on occasions, might return a NIL value.

xVal := IF(MyFunction() == NIL, DoThisFunction(), AnotherFunction() )

In the above example, the value that is returned by the MyFunction() UDF is compared to see if it contains a NIL value using the IF() function. Where the condition is true, in other words when MyFunction() has returned a value of NIL, the DoThisFunction() function is executed. However, if MyFunction() has returned any other type of value, then AnotherFunction() is run instead.

Earlier in this guide I suggested that most data types were valid for use in a database file. The NIL data type is one of a few that is not valid. In other words, you cannot create a database file that stores and "works with" the NIL data type.

Chapter 9: Programming Essentials <<     >> Chapter 11: Advanced Data Types