In this chapter, we will cover the following recipes:
Retrieving the system date and time
Retrieving the work date
Determining the day, month, and year from a given date
Using the date formula to calculate dates
Converting a value to a formatted string
Creating an array
Creating an option variable
Converting a string to another data type
Manipulating string contents
Data types are the base component in C/AL (Client/server Application Language) programming. Most of the data types are equivalent to the data types used in other programming language. Boolean, integer, decimal, dates, and strings are the most used data types in C/AL programming.
As developers, our job is to build a business tool that will manipulate the data input by users and make sure that data stored in tables is meaningful. Most of this data will be of the decimal, string, and date data types. NAV is, after all, a financial system at heart. At its most basic level, it cares about three things: "How much money?" (decimal), "What was it used for?" (string), and "When was it used?" (date).
The recipes in this chapter are very basic, but they will help you to understand the basics of C/AL coding. All recipes are accompanied by actual C/AL code from NAV objects.
Most times, we need to capture the system date and time of users' actions on NAV. This recipe will illustrate how to get the system date and time.
Let's create a new codeunit from Object Designer.
Now add the following code into the
OnRun
trigger of the codeunit:MESSAGE('Todays Date: %1\Current Time: %2', TODAY, TIME);
To complete the development of the codeunit, save and close it.
On executing the codeunit, you should see a window similar to the one in the following screenshot:
The TODAY
keyword returns the date and the TIME
keyword returns the time from the NAV Server system.
In the case of the older version of the NAV client—specifically the classic client—the date and time are taken from the client computer, which allows users to manipulate the system clock as per their personal requirement.
You can also retrieve the system date and time all at once using the CURRENTDATETIME
function. The date and time can be extracted using the
DT2DATE
and
DT2TIME
functions respectively.
The change log is a base NAV module that allows you to track changes to specific fields in tables. The following code can be found in the 423
, Change Log Management
codeunit in the
InsertLogEntry()
method:
ChangeLogEntry.INIT; ChangeLogEntry."Date and Time" := CURRENTDATETIME; ChangeLogEntry.Time := DT2TIME(ChangeLogEntry."Date and Time");
Here, instead of using the
WORKDATE
function, we use the CURRENTDATETIME
function and then extract the time using the
DT2TIME
function. The system designers can just do the following setup:
ChangeLogEntry.Date := TODAY; ChangeLogEntry.Time := TIME;
The advantage of using CURRENTDATETIME
over TODAY
and TIME
is minimal. CURRENTDATETIME
makes one request to the system while the second method makes two. It is possible that another operation or thread on the client machine could take over between retrieving the date and time from the computer; however, this is very unlikely. The operations could also take place right before and after midnight, generating some very strange data. The requirements for your modification will determine which method is best suited, but generally CURRENTDATETIME
is the correct method to use.
To perform tasks such as completing transactions for a date that is not the current date, you may have to temporarily change the work date. This recipe will show you how to determine what that actual work date is as well as when and where you should use it.
Navigate to Application Menu | Set Work Date or select the date in the status bar at the bottom of Microsoft Dynamics NAV.
Input the work date in the Work Date field or select it from the calendar.
Let's get started by creating a new codeunit from Object Designer.
Then add the following code into the
OnRun
trigger of the codeunit:MESSAGE('Work Date: %1\Todays Date: %2\Current Time: %3',WORKDATE, TODAY, TIME);
To complete the task, save and close the codeunit.
On executing the codeunit, you should see a window similar to the following screenshot:
To understand WORKDATE
, we have used two more keywords in this recipe. The work date is a date internal to the NAV system. This date is returned using the WORKDATE
keyword. It can be changed at any time by the user. The next date is TODAY
; it's a keyword to retrieve the present date that provides the date from the system. In the end, we used the
TIME
keyword, which provides current time information from the system clock.
Note
It is important to understand the difference between the NAV work date and the computer system date; they should be used in specific circumstances. When performing general work in the system, you should almost always use the WORKDATE
keyword. In cases where you need to log information and the exact date or time when an action occurred, you should use TODAY
or TIME
, or CURRENTDATETIME
.
The following code can be found in the 38
, Purchase Header
table, in the UpdateCurrencyFactor()
method:
IF "Posting Date" <> 0D THEN CurrencyDate := "Posting Date" ELSE CurrencyDate := WORKDATE;
Looking at this code snippet, we can see that if a user has not provided any specific posting date, the system will assign the value WORKDATE
as the default value for the posting date.
Sometimes it is necessary to retrieve only part of a date. NAV has built-in functions to do just that. We will show you how to use them in this recipe.
Let's create a new codeunit from Object Designer.
Then add the following global variables by navigating to View | C/AL Globals (Alt + V + B):
Name
Type
Day
Integer
Month
Integer
Year
Integer
Write the following code into the
OnRun
trigger of the codeunit:Day := DATE2DMY(TODAY, 1); Month := DATE2DMY(TODAY, 2); Year := DATE2DMY(TODAY, 3); MESSAGE('Day: %1\Month: %2\Year: %3', Day, Month, Year);
To complete the task, save and close the codeunit.
On executing the codeunit, you should see a window similar to the following screenshot:
The Date2DMY
function is a basic feature of NAV. The first parameter is a date variable. This parameter can be retrieved from the system using TODAY
or WORKDATE
. Additionally, a hardcoded date such as 01312010D
or a field from a table, such as Sales Header
or
Order Date
can be used as a first parameter. The second parameter is an integer that tells the function which part of the date to return. This number can be 1, 2, or 3, and corresponds to the day, month, and year (DMY) respectively.
The following code can be found in the 485
, Business Chart Buffer
table in the UpdateCurrencyFactor()
method of the GetNumberOfYears()
function:
EXIT(DATE2DMY(ToDate,3) - DATE2DMY(FromDate,3));
This function has two parameters of type date and it returns the value in integer. The basic usage of this function is to calculate the duration between two dates in terms of years.
The date formula allows us to determine a new date based on a reference date. This recipe will show you how to use the built-in CALCDATE
NAV function for date calculations.
Let's start by creating a new codeunit from Object Designer.
Add the following global variable by navigating to View | C/AL Globals (Alt + V + B):
Name
Type
CalculatedDate
Date
Write the following code into the
OnRun
trigger of the codeunit:CalculatedDate := CALCDATE('CM+1D', 010110D); MESSAGE('Calculated Date: %1', CalculatedDate);
Now save and close the codeunit.
On executing the codeunit, you should see a window similar to the following screenshot:
The CALCDATE()
function takes in two parameters: a calculation formula and a starting date. The calculation formula is a string that tells the function how to calculate the new date. The second parameter tells the function which date it should start with. A new date is returned by this function, so the value must be assigned to a variable.
The following units can be used in the calculation formula:
Unit |
Description |
---|---|
|
Day |
|
Weekday |
|
Week |
|
Month |
|
Quarter |
|
Year |
These units may be different depending on what language version NAV is running under.
You have two options to place the number before the unit. It can either be a standard number ranging between 1 and 9 or the letter C, which stands for current. These units can be added and subtracted to determine a new date based on any starting date.
Calculation formulas can become very complex. The best way to fully understand them is to write your own formulas to see the results. Start out with basic formulas such as 1M + 2W - 1D and move on to more complex ones, such as –CY + 2Q - 1W.
The following code is part of the CalcNumberOfPeriods()
function of the 485
, Business Chart Buffer
table:
"Period Length"::Week: NumberOfPeriods := (CALCDATE('<-CW>',ToDate)- CALCDATE('<CW>',FromDate)) DIV 7;
The preceding code snippet will return the difference between two dates in terms of weeks. <-CW>
will provide a week start date of ToDate
whereas <CW>
will provide a week end day of FromDate
. The difference between the calculated days will be divided by 7 to get the total number of weeks.
For more details on CALCDATE
, visit the following URL:
http://msdn.microsoft.com/en-us/library/dd301368(v=nav.70).aspx
Retrieving the system date and time
Retrieving the work date
Determining the day, month, and year from a given date
The Checking for conditions using an IF statement recipe in Chapter 2, General Development
There will be many occasions when you will need to display information in a certain way or multiple variable types on a single line. The FORMAT
function will help you change almost any data type into a string that can be manipulated in any way you see fit.
Let's get started by creating a new codeunit from Object Designer.
Then add the following global variable:
Name
Type
Length
FormattedDate
Text
30
Now write the following code into the
OnRun
trigger of the codeunit:FormattedDate := FORMAT(TODAY, 0, '<Month Text> <Day,2>,<Year4>'); MESSAGE('Today is %1', FormattedDate);
To complete the task, save and close the codeunit.
On executing the codeunit, you should see a window similar to the following screenshot:
The FORMAT
function takes one to three parameters. The first parameter is required and can be of almost any type: date, time, integer, decimal, and so on. This parameter is returned as a string.
The second parameter is the length of the string to be returned. The default, zero, means that the entire string will be returned, a positive number tells the function to return a string of exactly that length, and a negative number returns a string not larger than that length.
There are two options for the third, and final, parameter. One is a number, representing a predefined format you want to use for the string, and the other is a literal string. In the example, we used the actual format string. The text contained in the angular brackets (<
>)
will be parsed and replaced with the data in the first parameter.
Note
There are many predefined formats for dates. Run a search for Format Property
in the Developer and IT Pro Help option in the Help menu of Microsoft NAV Development Environment or visit the following URL:
http://msdn.microsoft.com/en-us/library/dd301059(v=nav.70).aspx
The following code can be found on the
OnValidate()
trigger of the Starting Date
field from the 50
, Accounting Period
table:
Name := FORMAT("Starting Date",0,Text000);
In the preceding code, Text000
is a text constant and carries the <Month Text>
value. This code will return month of "Starting Date"
in text format.
Retrieving the system date and time
Retrieving the work date
Determining the day, month, and year from a given date
Converting a string to another data type
The Checking for conditions using an IF statement recipe in Chapter 2, General Development
The Advanced filtering recipe in Chapter 3, Working with Tables, Records, and Queries
The Retrieving data using the FIND and GET statements recipe in Chapter 3, Working with Tables, Records, and Queries
Creating multiple variables to store related information can be time consuming. It leads to more code and more work. Using an array to store related and similar types of information can speed up development and lead to much more manageable code. This recipe will show you how to create and access array elements.
Let's create a new codeunit from Object Designer.
Add the following global variables by navigating to View | C/AL Globals (Alt + V + B):
Name
Type
i
Integer
IntArray
Integer
Now, with the cursor on the
IntArray
variable, navigate to View | Properties (Shift + F4).In the Property window, set the following property:
Property
Value
Dimensions
10
Write the following code into the
OnRun
trigger of the codeunit:FOR i := 1 TO ARRAYLEN(IntArray) DO BEGIN IntArray[i] := i; MESSAGE('IntArray[%1] = %2', i, IntArray[i]); END;
To complete the task, save and close the codeunit.
On executing the codeunit, you should see a window similar to the following screenshot:
An array is a single variable that holds multiple values. The values are accessed using an integer index. The index is passed within square brackets ([
]
).
Note
NAV provides several functions to work with arrays. For instance, ARRAYLEN
returns the number of dimensions of the array and COPYARRAY
will copy all of the values from one array into a new array variable. You can find a complete list of the array functions in the Developer and IT Pro Help option in the Help menu of Microsoft NAV Development Environment.
Open the 365
, Format Address
codeunit. Notice that the first function, FormatAddr
, has a parameter that is an array. This is the basic function that all of the address formats use. It is rather long, so we will discuss only a few parts of it here.
This first section determines how the address should be presented based on the country of the user. Variables are initialized depending on which line of the address should carry certain information. These variables will be the indexes of our array.
CASE Country."Contact Address Format" OF Country."Contact Address Format"::First: BEGIN NameLineNo := 2; Name2LineNo := 3; ContLineNo := 1; AddrLineNo := 4; Addr2LineNo := 5; PostCodeCityLineNo := 6; CountyLineNo := 7; CountryLineNo := 8; END;
Then we will fill in the array values in the following manner:
AddrArray[NameLineNo] := Name; AddrArray[Name2LineNo] := Name2; AddrArray[AddrLineNo] := Addr; AddrArray[Addr2LineNo] := Addr2;
Scroll down and take a look at all of the other functions. You'll see that they all take in an array as the first parameter. It is always a text array of length 90
with eight dimensions. These are the functions you will call when you want to format an address. To use this codeunit correctly, we will need to create an empty array with the specifications listed before and pass it to the correct function. Our array will be populated with the appropriately formatted address data.
Manipulating string contents
The Using the CASE statement to test multiple conditions recipe in Chapter 2, General Development
If you need to force the user to select a value from a predefined list, an option is the way to go. This recipe explains how to create an Option
variable and access each of its values.
Let's create a new codeunit from Object Designer.
Then add the following global variable:
Name
Type
ColorOption
Option
With the cursor on the
ColorOption
variable, navigate to View | Properties or (Shift + F4).In the Property window, set the following property:
Property
Value
OptionString
None,Red,Green,Blue
Now write the following code into the
OnRun
trigger of the codeunit:ColorOption := ColorOption::Green; CASE ColorOption OF ColorOption::None: MESSAGE('No Color Selected'); ColorOption::Red: MESSAGE('Red'); ColorOption::Green: MESSAGE('Green'); ColorOption::Blue: MESSAGE('Blue'); END;
Save and close the codeunit.
On executing the codeunit, you should see a window similar to the one shown in the following screenshot:
An Option
is a field or variable that stores one value from a selectable list. In a form, this list will appear as a dropdown from which the user can select a value. The list of options is stored as a comma-separated string in the
OptionString
property. If we query such stored values from a SQL database, we will receive an integer value representing each option. In our current example, the integer value has been mapped with options, where None
= 0
, Red
= 1
, Green
= 2
, and Blue
= 3.
These values are accessed using the variable_name::option_name
syntax. The first line of the example assigns one of the possible values (Green
) to the variable. Then we use a CASE
statement to determine which of the values were selected.
The Option
fields are prevalent throughout the NAV system, but most commonly on documents. In NAV, many documents share the same table. For example, sales quotes, orders, invoices, and return orders are all based on the Sales Header
table. In order to distinguish between the types, there is an Option
field called Document Type
. Design the 36
, Sales Header
table to see the available options for this field.
Now design the 80
, Sales-Post
codeunit. Examine the OnRun
trigger. At the start of the function, you will see the following code:
CASE "Document Type" OF "Document Type"::Order: Receive := FALSE; "Document Type"::Invoice: BEGIN Ship := TRUE; Invoice := TRUE; Receive := FALSE; END; "Document Type"::"Return Order": Ship := FALSE; "Document Type"::"Credit Memo": BEGIN Ship := FALSE; Invoice := TRUE; Receive := TRUE; END; END;
This is a common example of how options are used in NAV. You can scroll through the codeunit to find more examples.
The Using the CASE statement to test multiple conditions recipe in Chapter 2, General Development
Sometimes, a string representation isn't enough. In order to perform certain actions, you need your data to be in a certain format. For example, we are reading data from a text file, so our entire data is simple text, which needs to be converted into an appropriate data type to use it in NAV. This recipe will show you how to change that data into a format that you can use.
Let's start by creating a new codeunit from Object Designer.
Now add the following global variables:
Name
Type
Length
DateText
Text
30
DateValue
Date
Write the following code into the
OnRun
trigger of the codeunit:DateText := '01/10/2012'; EVALUATE(DateValue, DateText); MESSAGE('Microsoft Dynamics NAV 2013 launch date is %1', DateValue);
To complete the development, save and close the codeunit.
On executing the codeunit, you should see a window similar to the one shown in the following screenshot:
The EVALUATE()
function takes in two parameters. The first is a variable of the type that we want our value to be converted into. This could be date, time, Boolean, integer, or any other simple data type. This parameter is passed by reference, meaning that the result of the function is stored in that variable. There is no need to do a manual assignment to get a return value.
The second parameter is the string that you need to convert. This text is usually stored in a field or variable, but can also be hardcoded.
The EVALUATE()
function is widely used in NAV C/AL code. The following code snippet is taken from the
CheckCreditCardData()
function of the 825
, Do Payment Mgt
codeunit:
EVALUATE (IntValue1,FORMAT(TODAY,0,'<Year>')); EVALUATE (IntValue2,COPYSTR(DOPaymentCreditCard."Expiry Date",3,2)); IF IntValue1 > IntValue2 THEN ERROR (Text006, CreditCardNo, DOPaymentCreditCard.FIELDCAPTION ("No."));
Before completing a transaction, the credit card's validity period needs to be checked. The preceding code extracts the year from the current date provided by the TODAY
function and the expiry date of the credit card. Both the values are evaluated using the relational operator. If the card has expired, the system will execute a predefined error message in text constant Text006
.
It can be very useful to parse a string and retrieve certain values. This recipe will show you how to examine the contents of a string and manipulate that data.
Let's create a new codeunit from Object Designer.
Add a function called
RemoveNonNumeric
. It should return a text variable calledNewString
.Add the following parameters for the same function:
Name
Type
Length
String
Text
30
Now add the following global variables:
Name
Type
Length
OldPhoneNumber
Text
30
NewPhoneNumber
Text
30
I
Integer
Write the following code to the
RemoveNonNumeric
function:FOR i := 1 TO STRLEN(String) DO BEGIN IF String[i] IN ['0', '1', '2', '3', '4', '5', '6', '7','8','9'] THEN NewString := NewString + FORMAT(String[i]); END;
Write the following code into the
OnRun
trigger of the codeunit:OldPhoneNumber := '(230) 299-876'; NewPhoneNumber := RemoveNonNumeric(OldPhoneNumber); MESSAGE('Old Phone Number: %1\New Phone Number: %2', OldPhoneNumber, NewPhoneNumber);
To complete the task, save and close the codeunit.
On executing the codeunit, you should see a window similar to the one shown in the following screenshot:
A string is actually an array of characters. The same array syntax will be used to access the individual characters of the string.
We start with a FOR
loop that begins at the first character, with index 1
, and goes on until we reach the end of our string. This is determined using the STRLEN()
function, which stands for string length. As the first index is 1
, the last index will be N
or the number of characters in the string.
Next, we access the character at that index using square brackets. If the character is a number, meaning we want to keep it because it is a numeric value, we add it to our resulting string.
Note
NAV comes with plenty of built-in string manipulation functions to remove characters, return substrings, find characters within strings, and many more. A search in the Developer and IT Pro Help option of the Help menu of Microsoft NAV Development Environment for string functions will give you a complete list.
The CheckIBAN
function of the 79
, Company Information
table is a simple example of string manipulation to validate
IBAN (International Bank Account Number). IBAN is internationally agreed on and adopted. It consists of up to 34 alphanumeric characters: the first two letters are the country code, then two check digits, and finally a country-specific Basic Bank Account Number. The same is validated by manipulating the input string using various functions. The following code gives you an example for the same:
IF IBANCode = '' THEN EXIT; IBANCode := DELCHR(IBANCode); Modulus97 := 97; IF (STRLEN(IBANCode) <= 5) OR (STRLEN(IBANCode) > 34) THEN IBANError; ConvertIBAN(IBANCode); WHILE STRLEN(IBANCode) > 6 DO IBANCode := CalcModulus(COPYSTR(IBANCode,1,6),Modulus97) + COPYSTR(IBANCode,7); EVALUATE(I,IBANCode); IF (I MOD Modulus97) <> 1 THEN IBANError;
There are a few more functions used to validate the string; such as ConvertIBAN
, CalcModulus
, and ConvertLetter
. These functions can give you a basic idea to write your own code.
For more complex examples, please follow the
DecomposeRowID()
function in the 6500
, Item Tracking Management
codeunit. The code evaluates the value stored in the Source RowId
field of the 6508
, Value Entry Relation
table.