This program is a more complicated version of the basic File I/O tutorial (here) and will involve string manipulation (tutorial here) as well as time (tutorial here). We are going to create a program which takes a file as an input, gets the individuals lines out of that string, extracts times from these lines, and then outputs a total to a file.
Setting Up
After you have created this program we need to do several things, first, we need to add the log-handler file to the project. To do this you need to copy log-handler.sma
into the folder which has the same name as your project within the project folder in my case \AdvancedIO\AdvancedIO
this will allow you to use the file in your program. The next thing you need to do is add the following libraries to your project in Project Settings
We are going to come back to the Project Settings later on but for now let’s declare the variables we will need
Variable Declaration
constant sTIMEFORMAT "0h:mm:ss"
function main()
string sInputfilename
string sOutputfilename;
integer iErrorno;
string sLogfile;
string sLine;
string sDay
time tSubTotal;
time tTotal;
LogHandler log_handler;
fsfileinputstream inputfile;
fsfileoutputstream outputfile;
These are mostly self-explanatory, the first two variables are for input and output file names, iErrorno
is used to deal with errors in opening these files, sLogfile
is going to be the name we give to our log file. sLine
and sDay
are both used within our string manipulation one as the line we get from the file, the other as a placeholder in our string logic. tSubTotal
and tTotal
are temporary variables we will use as output. Finally log_handler
,inputfile
,outputfile
are all objects we need to declare and will act as our log handler, input, and output.
N.B. The naming scheme I am using is for visibility, any non-object will be prefaced with the first letter of its type, you can, of course, use whatever names you want
Variable Initialisation
To do anything useful we will need to initialise the variables we have just created.
sInputfilename = "examples.txt"
sOutputfilename = "AdvancedIOOutput.txt"
sLogfile = "AdvancedIO.log";
iErrorno = 0
sDay = ""
tSubTotal =@ time.new()
tTotal =@ time.new()
tSubTotal.set(0,0,0,0,0);
tTotal.set(0,0,0,0,0);
This part is mostly self-explanatory with the exception of the time variables, to use this properly requires object like initialisation and then setting their value.
The object initialisation should also be self-explanatory at this point, however, I want to point out that there is no need to add the error variable, and we will not be covering error handling in this tutorial. If however, you want to do error handling that should go directly after the initialisation. Not including the variable means that if the program runs into an issue it will stop execution and output an error code, as it is in this program execution will continue but will not work as intended.
log_handler =@ LogHandler.new(sLogfile);
inputfile =@ fsfileinputstream.new(sInputfilename,iErrorno);
outputfile =@ fsfileoutputstream.new(sOutputfilename,iErrorno);
Loop
Once we have declared all our variables we can start our loop. The first thing to do is to read the file line by line:
while inputfile.endofdata != .true
sLine = inputfile.getstring(5000,1,terminator="{d}{a}");
This code should look familiar to you if you have completed the File I/O tutorial. It will get the first 5000 characters or until it finds a newline terminator. This next part will vary from use case to use case, in our case the text file has the following layout:
W30 2018-07-25 Wed @7 SIMPOL: Bug fixes 5:15:56 6:57:40 1:41:44 @6 Accounting, budgeting 16:18:51 17:13:36 0:54:45 @5 SIMPOL: Bug fixes 17:13:41 18:27:53 1:14:12 @4 SIMPOL: Bug fixes 21:11:26 21:56:06 0:44:40 4:35:21
In this example we want to do the following total all the SIMPOL times for each day, to do this we will need to separate each day of the week and then total all the times that contain the keyword we are looking for. To separate the days of the week we use the following if statement:
if .instr(sLine,"Mon") != 0 or (sDay == "Mon" and .lstr(sLine,1) !="W")
This statement is relatively complex, to return true it requires one of the following to be true: in the line, we find the string “Mon” (for Monday), or our temporary variable is equal to monday and the first character in the left of the string is not equal to “W”. This second condition is true whenever we are trying to total times within a day, and have not reached a new day.
If we are starting a new day we need to do several things: output the sub-total of the last day, if and only if this is not the first day, add the sub-total to the overall total, select the current day (set the temporary variable) and reset the tSubTotal
to zero.
if sDay != "Mon"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Mon"
tSubTotal.set(0,0,0,0,0)
end if
Function 1
The following piece of code will be repeated often it is thus best to put it into a function. This function will extract the start and end time from the line and output the difference between the two. This function looks something like this:
function getTime(string sLine,time tTotal)
if .instr(sLine, "SIMPOL:") != 0 and sLine != .nul;
string temp;
string sStartTime;
string sEndTime;
time tStartTime;
time tEndTime;
tEndTime = time.new();
tStartTime = time.new();
sLine = .rstr(sLine, 35)
sLine = rtrim(sLine)
sLine = ltrim(sLine)
temp = .lstr(sLine, 17)
temp = rtrim(temp)
sStartTime = .lstr(temp,8)
sStartTime = rtrim(sStartTime)
sEndTime = .rstr(temp,8)
sEndTime = ltrim(sEndTime)
tStartTime = string2time(sStartTime,sTIMEFORMAT);
tEndTime = string2time(sEndTime,sTIMEFORMAT);
tTotal = tTotal + (tEndTime - tStartTime);
end if
end function tTotal
This function may look complicated, but it’s not really, the first thing we do is test if in the line we pass into the function we can find our matching phrase, in our case "SIMPOL:"
, we also check that the line is not a null object. Once this check has run we can declare our variables and initialise our variables
The next thing we do is to get the first 35 characters on the right of the line, why 35 characters exactly? Because in our case that is the longest a set of times can be. We then want to trim all spaces from the left and right. The spaces on the left have the added complication that if the description is long enough characters will appear on the very left and interfere with our numbers if there were a pattern to this interference we could then add that pattern to the sCharList
variable in ltrim
.
Now we can start separating the start and end times, this is done by taking 17 characters from the left and then trimming it again. Now we have something containing both the start and end times, we need to split this up by taking 8 characters from either side and making these separate strings. We then use string2time
to convert our start and end time to proper times. The final thing we need to do is to get an output. In our case, we will add the total time we got as an input to the difference between the start and end time we’ve just formatted.
Copy-Paste
Once this has been done we now need to copy and paste the daily subtotal code we made before this function and change our sDay
variable to different days: “Mon”, “Tue”, “Wed”, “Thu”, “Fri”, “Sat”, “Sun”. The code should now look almost identical to the completed code (below). The last thing to do now is to output the sum total to the file:
outputfile.putstring("Total: " + .tostr(tTotal.hours(.true),10) + ":" + .tostr(tTotal.minutes(),10) + ":" + \
.tostr(tTotal.seconds(),10) + "{d}{a}",1);
N.B. There is in the current version of time a bug where you need to set the variable in hours to .true to get accurate times otherwise it will reset every 24 hours
Additional Functionality
This program will now work perfectly well however, it is not amazingly easy to use especially if we are wanting to change the file often, to fix this we are going to add a command-line target to change the input file, this is quite easy to do we need to add an input into our main function, similar to any other function. The main function should now start like this:
function main(string inputfilename)
This will then allow you to in project settings set a command line parameter, similarly, this will allow you to launch the program using the command line and set the input file.
This step is needed for the next part of this tutorial
Output
If you have done everything right you should get the following output from the exampe file
Tue subtotal: 3:14:25 Wed subtotal: 3:40:36 Thu subtotal: 4:56:55 Total: 11:51:56
Follow On
The next part of this tutorial will concern itself with the building of a GUI interface for this program and can be found (here)
Downloads & Further Learning
Full Code
constant sTIMEFORMAT "0h:mm:ss"
function main(string sInputfilename)
//Variables declaration
string sOutputfilename;
integer iErrorno;
string sLogfile;
string sLine;
string sDay
time tSubTotal;
time tTotal;
//Object declaration
LogHandler log_handler;
fsfileinputstream inputfile;
fsfileoutputstream outputfile;
//Variable initalisation
sOutputfilename = "AdvancedIOOutput.txt"
sLogfile = "AdvancedIO.log";
iErrorno = -1
sDay = ""
tSubTotal =@ time.new()
tTotal =@ time.new()
tSubTotal.set(0,0,0,0,0);
tTotal.set(0,0,0,0,0);
//Object initialisation
log_handler =@ LogHandler.new(sLogfile);
inputfile =@ fsfileinputstream.new(sInputfilename,iErrorno);
outputfile =@ fsfileoutputstream.new(sOutputfilename,iErrorno);
while inputfile.endofdata != .true
sLine = inputfile.getstring(5000,1,terminator="{d}{a}");
//Monday
if .instr(sLine,"Mon") != 0 or (sDay == "Mon" and .lstr(sLine,1) !="W")
if sDay != "Mon"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Mon"
tSubTotal.set(0,0,0,0,0)
end if
tSubTotal = getTime(sLine,tSubTotal)
//Tuesday
else if .instr(sLine,"Tue") != 0 or (sDay == "Tue" and .lstr(sLine,1) !="W")
if sDay != "Tue"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Tue"
tSubTotal.set(0,0,0,0,0)
end if
tSubTotal = getTime(sLine,tSubTotal)
//Wednesday
else if .instr(sLine,"Wed") != 0 or (sDay == "Wed" and .lstr(sLine,1) !="W")
if sDay != "Wed"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Wed"
tSubTotal.set(0,0,0,0,0)
end if
tSubTotal = getTime(sLine,tSubTotal)
//Thursday
else if .instr(sLine,"Thu") != 0 or (sDay == "Thu" and .lstr(sLine,1) !="W")
if sDay != "Thu"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Thu"
tSubTotal.set(0,0,0,0,0)
end if
tSubTotal = getTime(sLine,tSubTotal)
//Friday
else if .instr(sLine,"Fri") != 0 or (sDay == "Fri" and .lstr(sLine,1) !="W")
if sDay != "Fri"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Fri"
tSubTotal.set(0,0,0,0,0)
end if
tSubTotal = getTime(sLine,tSubTotal)
//Saturday
else if .instr(sLine,"Sat") != 0 or (sDay == "Sat" and .lstr(sLine,1) !="W")
if sDay != "Sat"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Sat"
tSubTotal.set(0,0,0,0,0)
end if
tSubTotal = getTime(sLine,tSubTotal)
//Sunday
else if .instr(sLine,"Sun") != 0 or (sDay == "Sun" and .lstr(sLine,1) !="W")
if sDay != "Sun"
if sDay != ""
outputfile.putstring(sDay + " subtotal: " + .tostr(tSubTotal.hours(),10) + ":" + .tostr(tSubTotal.minutes(),10) + ":" + .tostr(tSubTotal.seconds(),10)+ "{d}{a}",1);
end if
tTotal = tTotal + tSubTotal
sDay = "Sun"
tSubTotal.set(0,0,0,0,0)
end if
tSubTotal = getTime(sLine,tSubTotal)
end if
end while
outputfile.putstring("Total: " + .tostr(tTotal.hours(.true),10) + ":" + .tostr(tTotal.minutes(),10) + ":" + .tostr(tTotal.seconds(),10) + "{d}{a}",1) ;
end function ""
function getTime(string sLine,time tTotal)
if .instr(sLine, "SIMPOL:") != 0 and sLine != .nul;
//Declaration of variables
string temp;
string sStartTime;
string sEndTime;
time tStartTime;
time tEndTime;
//Convert time strings to time objects (Need to initalise before can be used properly) (also set, if not being set elsewhere)
tEndTime = time.new();
tStartTime = time.new();
//Get all times
sLine = .rstr(sLine, 35)
sLine = rtrim(sLine)
sLine = ltrim(sLine,", simpol")
//Get just start and end time
temp = .lstr(sLine, 17)
temp = rtrim(temp)
//Get Start time
sStartTime = .lstr(temp,8)
sStartTime = rtrim(sStartTime)
//Get End time
sEndTime = .rstr(temp,8)
sEndTime = ltrim(sEndTime)
tStartTime = string2time(sStartTime,sTIMEFORMAT);
tEndTime = string2time(sEndTime,sTIMEFORMAT);
tTotal = tTotal + (tEndTime - tStartTime);
end if
end function tTotal