Progress 4GL: How to generate different log file name when multiple processes using the same Timestamp? - openedge

I am using this code for generating log files with timestamp so that each time when the procedure called by one process it will generate unique log filename. But the concern is that when two processes calling the same procedure at a time then the log files got over written. I don't want this. I want to find when procedures called by multiple processes and need to generate different log file name.
DEFINE VARIABLE TimeStamp AS CHARACTER NO-UNDO.
DEFINE VARIABLE cPath AS CHARACTER NO-UNDO.
ASSIGN
TimeStamp = string(month(today),"99") + string(day(today),"99") +~
string(year(today),"9999") + substring(string(time,"hh:mm:ss"),1,2) +~
substring(string(time,"hh:mm:ss"),4,2) +~
substring(string(time,"hh:mm:ss"),7,2)
cPath = "cim_" + STRING(TimeStamp) + ".log".
PROCEDURE generatecimlogfile:
OUTPUT TO VALUE(cPath).
MESSAGE "Inside a file".
END PROCEDURE.
RUN generatecimlogfile. /*(This procedure called by multiple processes at a time).*/

Instead of building a timestamp down to the second, you can build it to the millisecond. The NOW function will give you a datetime to the millisecond. This code will return such a timestamp:
DEFINE VARIABLE cTimestamp AS CHARACTER NO-UNDO.
RUN createTimestamp (OUTPUT cTimestamp).
MESSAGE cTimestamp VIEW-AS ALERT-BOX INFORMATION.
PROCEDURE createTimestamp :
DEFINE OUTPUT PARAMETER pcTimeStr AS CHARACTER NO-UNDO.
DEFINE VARIABLE cNow AS CHARACTER NO-UNDO.
DEFINE VARIABLE dtDate AS DATE NO-UNDO.
DEFINE VARIABLE cDate AS CHARACTER NO-UNDO.
DEFINE VARIABLE cTime AS CHARACTER NO-UNDO.
cNow = STRING(NOW, "99/99/9999 HH:MM:SS.SSS").
cDate = ENTRY(1, cNow, " ").
ASSIGN dtDate = DATE(cDate) NO-ERROR.
cTime = ENTRY(2, cNow, " ").
cTime = REPLACE(cTime, ":", "").
cTime = REPLACE(cTime, ".", "").
cTime = REPLACE(cTime, ",", "").
ASSIGN pcTimeStr = STRING(YEAR(dtDate), "9999") + STRING(MONTH(dtDate), "99") + STRING(DAY(dtDate), "99") + cTime NO-ERROR.
IF pcTimeStr = ? THEN pcTimeStr = "00000000000000000".
END PROCEDURE.
If that's not good enough, you can guarantee a unique filename by adding a GUID to the timestamp:
DEFINE VARIABLE cTimestamp AS CHARACTER NO-UNDO.
DEFINE VARIABLE cGUID AS CHARACTER NO-UNDO.
cGUID = GUID(GENERATE-UUID).
cTimestamp = "20200802123456789" + cGUID.
MESSAGE cTimestamp VIEW-AS ALERT-BOX INFORMATION.

Related

Using num-entries() with more than one possible delimitor

I have a list of email addresses that I am reading in from a csv file, that are sometimes separated by commas and sometimes by semicolons when the person has more than 1 email address.
Examples:
Pin email_address
11 heidi#gmail.com,hh#yahoo.com
12 tom#osu.edu;TQ#gmail.com
13 lisa#yahoo.com
14 linda#me.com;llewis#gmail.com,lvlv#yahoo.com
Let's say I am reading these into a variable and counting them per person with another variable:
DEFINE VARIABLE emailString AS CHARACTER NO-UNDO.
DEFINE VARIABLE iEmailCount AS INTEGER.
I want to count the number of emails for the person. I know that I can use this syntax to count the entries between semi-colons, and between commas respectively.
iEmailCount = NUM-ENTRIES (emailString, ";").
iEmailCount = NUM-ENTRIES (emailString).
But how can I best say...
iEmailCount = Num_ENTRIES(emailString,";" OR ",").
You have to break the line down by each delimiter. Loop through one delimiter, then the second:
DEFINE VARIABLE emailString AS CHARACTER NO-UNDO.
DEFINE VARIABLE iEmailCount AS INTEGER NO-UNDO.
DEFINE VARIABLE iLoop AS INTEGER NO-UNDO.
iEmailCount = NUM-ENTRIES(emailString).
DO iLoop = 1 TO NUM-ENTRIES(emailString): /* Loop through comma delimiters */
iEmailCount = iEmailCount + NUM-ENTRIES(ENTRY(iLoop, emailString), ";") - 1. /* Sum by semicolon delimiter */
END.
If you're sure that a semicolon will only appear as a delimiter in the data, you can replace them with commas. Then it's a simple count of the number of comma entries:
DEFINE VARIABLE emailString AS CHARACTER NO-UNDO INITIAL "linda#me.com;llewis#gmail.com,lvlv#yahoo.com".
DEFINE VARIABLE iEmailCount AS INTEGER NO-UNDO.
emailString = REPLACE(emailString, ";", ",").
iEmailCount = NUM-ENTRIES(emailString).
MESSAGE "Email string: " emailString SKIP
"Count: " iEmailCount VIEW-AS ALERT-BOX.
You can't in a single statement.
DEFINE VARIABLE emailString AS CHARACTER NO-UNDO.
DEFINE VARIABLE iEmailCount AS INTEGER.
iEmailCount = NUM-ENTRIES (emailString, ";") + NUM-ENTRIES (emailString).
For your pin 14 you will need to loop through the string, and count the entries manually.
define variable pin as character.
define variable pos as integer.
define variable pos2 as integer.
define variable cnt as integer.
define variable addr as character.
define variable startAt as integer.
pin = 'linda#me.com;llewis#gmail.com,lvlv#yahoo.com'.
startAt = 1.
pos = index(pin, ',', startAt).
do while pos gt 0:
addr = substring(pin, startAt, pos - 1).
pos2 = index(addr, ';').
if pos2 gt pos then
cnt = cnt + 1.
else
cnt = cnt + num-entries(addr, ';').
startAt = pos + 1.
pos = index(pin, ',', startAt).
end.
// after the last ,
if not trim(substring(pin, StartAt)) eq '' then
cnt = cnt + 1.
message
pin skip
cnt
view-as alert-box.
Just a gotcha, a string with a space will return 1 . An empty string will return 0.
message
'empty: ' num-entries('') skip // 0
'space: ' num-entries(' ') // 1
view-as alert-box.

Progress 4GL - change font color of specific values while exporting them as .csv file

I have a situation where I need to export all the data available in temp table as .csv file. Below query helping me only export as .csv file but not sure how to write the logic to change the font color and cell. Please help me because I have a limited knowledge in progress 4GL.
DEFINE TEMP-TABLE tt_data NO-UNDO
FIELD cData1 AS CHARACTER
FIELD cData2 AS CHARACTER
.
CREATE tt_data.
ASSIGN
cData1 = "FORD"
cData2 = "TATA"
.
OUTPUT TO "C:\temp\test.csv".
FOR EACH tt_data NO-LOCK:
EXPORT DELIMITER "," tt_data.
END.
OUTPUT CLOSE.
CSV dies not support any formatting instructions such as colors
Try the SYLK format instead. http://www.pindari.com/sylk.html
Using Excel VBA
https://learn.microsoft.com/en-us/office/vba/api/overview/excel
DEFINE TEMP-TABLE tt_data NO-UNDO
FIELD cData1 AS CHARACTER
FIELD cData2 AS CHARACTER
.
CREATE tt_data.
ASSIGN
cData1 = "FORD"
cData2 = "TATA"
.
DEFINE VARIABLE vExcApp AS COM-HANDLE NO-UNDO.
DEFINE VARIABLE vWrkBok AS COM-HANDLE NO-UNDO.
DEFINE VARIABLE vWrkSht AS COM-HANDLE NO-UNDO.
DEFINE VARIABLE vWrkRng AS COM-HANDLE NO-UNDO.
DEFINE VARIABLE iCnt AS INTEGER NO-UNDO INIT 0.
CREATE "Excel.Application" vExcApp NO-ERROR.
IF ERROR-STATUS:ERROR OR NOT VALID-HANDLE(vExcApp) THEN
DO:
MESSAGE "Excel Application not installed"
VIEW-AS ALERT-BOX ERROR BUTTONS OK.
RETURN.
END.
vExcApp:VISIBLE = FALSE.
vWrkBok = vExcApp:Workbooks:ADD.
vWrkSht = vExcApp:Sheets:ADD.
FOR EACH tt_data NO-LOCK:
iCnt = iCnt + 1.
vWrkRng = vWrkSht:Range("A" + STRING(iCnt)).
vWrkRng:VALUE = tt_Data.cData1.
vWrkRng:Interior:Color = RGB-VALUE (240,240,240).
vWrkRng = vWrkSht:Range("B" + STRING(iCnt)).
vWrkRng:VALUE = tt_Data.cData2.
vWrkRng:Interior:Color = RGB-VALUE (240,0,240).
END.
vExcApp:VISIBLE = TRUE.
IF VALID-HANDLE(vWrkRng) THEN RELEASE OBJECT vWrkRng.
IF VALID-HANDLE(vWrkSht) THEN RELEASE OBJECT vWrkSht.
IF VALID-HANDLE(vWrkBok) THEN RELEASE OBJECT vWrkBok.
IF VALID-HANDLE(vExcApp) THEN RELEASE OBJECT vExcApp.

How to add html tables in progress 4gl?

I created a temp table in Progress4gl and I need to email the data from temp table using html syntax. Which means I need to link all the fields in temp table to html table and email.
The fields in temp table are:
Part_ID, CustomerPartID, customer
Please help me with this.
Well you could just output the HTML. Something like this:
define temp-table tt_test
field f1 as integer
field f2 as character
field f3 as date
.
create tt_test.
assign
f1 = 1
f2 = "abc"
f3 = today
.
create tt_test.
assign
f1 = 2
f2 = "xyz"
f3 = today + 30
.
output to value( "mytable.html" ).
put unformatted "<table>" skip.
for each tt_test:
put unformatted substitute( " <tr><td>&1</td><td>&2</td><td>&3</td></tr>", f1, f2, f3 ) skip.
end.
put unformatted "</table>" skip.
output close.
For creating an html table you can (ab)use the power of datasets combined with serialize-name:
/* Write some awesome ABL code here, or load an existing snippet! */
define temp-table ttparts serialize-name "tr"
field part_id as char serialize-name "td"
field customerPartID as char serialize-name "td"
field customer as char serialize-name "td"
.
define dataset ds serialize-name "table" for ttparts .
define buffer bupart for ttparts.
create bupart.
assign
bupart.part_id = "one"
bupart.customer = "A"
.
create bupart.
assign
bupart.part_id = "two"
bupart.customer = "B"
.
def var lcc as longchar no-undo.
dataset ds:write-xml( "longchar", lcc, true ).
message string( lcc ).
https://abldojo.services.progress.com:443/#/?shareId=5d1618c14b1a0f40c34b8bc8
An updated version which accepts any temp-table handle and will return a longchar containing the HTML table:
function createHtmlTable returns longchar (
i_ht as handle
):
def var lcc as longchar.
def var hds as handle.
def var hb as handle.
def var ic as int.
create dataset hds.
hds:serialize-name = "table". // not needed if stripped below
create buffer hb for table i_ht.
hb:serialize-name = "tr".
hds:add-buffer( hb ).
do ic = 1 to hb:num-fields:
hb:buffer-field( ic ):serialize-name = "td".
end.
hds:write-xml( "longchar", lcc, true ).
// remove xml declaration
lcc = substring( lcc, index( lcc, "<", 2 ) ).
entry( 1, lcc, ">" ) = "<table". // see comment above
return lcc.
finally:
delete object hds.
end finally.
end function.
define temp-table ttparts
field part_id as char
field customerPartID as char
field customer as char
.
define buffer bupart for ttparts.
create bupart.
assign
bupart.part_id = "one"
bupart.customer = "A"
.
create bupart.
assign
bupart.part_id = "two"
bupart.customer = "B"
.
message string( createHtmlTable( temp-table ttparts:handle ) ).
https://abldojo.services.progress.com/#/?shareId=5d1760d84b1a0f40c34b8bcd
There is no built-in method to do this. One way that I could think of is converting the temp table to json and then json to HTML. I believe there are several utilities to convert json to html table. You can convert temp table to json in ABL using WRITE-JSON method.
An example from Progress documentation:
DEFINE VARIABLE cTargetType AS CHARACTER NO-UNDO.
DEFINE VARIABLE cFile AS CHARACTER NO-UNDO.
DEFINE VARIABLE lFormatted AS LOGICAL NO-UNDO.
DEFINE VARIABLE lRetOK AS LOGICAL NO-UNDO.
DEFINE TEMP-TABLE ttCust NO-UNDO LIKE Customer.
/* Code to populate the temp-table */
ASSIGN
cTargetType = "file"
cFile = "ttCust.json"
lFormatted = TRUE.
lRetOK = TEMP-TABLE ttCust:WRITE-JSON(cTargetType, cFile, lFormatted).

Code to add x minutes to time in fillin field

I'm a beginner to programming and to Progress, first post on StackOverflow, hope I'm posting in the right place!
I have a fillin field where I enter a time (hh:mm), character format. I also have two arrows, one pointing forward and one backward, and I want them to add / subtract 20 min respectively when pushed.
What would be a good way to write code for this? Turn the current time value to integer and seconds past midnight and then add /subtract 1200 sec? How would I get the result back to a hh:mm format to display in the fillin?
Any help greatly appreciated!
/Ellen
I think this will do what you need. Have the ON CHOOSE triggers on your arrow buttons run the changeMins procedure. Pass in the character time string from your fill-in and either "Add" or "Subtract". The output value will be the new adjusted time string. You can then set the screen value of your fill-in to that output value.
DEFINE VARIABLE cTime AS CHARACTER NO-UNDO.
cTime = "12:45".
RUN changeMins (INPUT-OUTPUT cTime, INPUT "Add").
MESSAGE cTime VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
PROCEDURE changeMins:
DEFINE INPUT-OUTPUT PARAMETER pcTime AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcAction AS CHARACTER NO-UNDO.
DEFINE VARIABLE iHr AS INTEGER NO-UNDO.
DEFINE VARIABLE iMn AS INTEGER NO-UNDO.
/* Split the time string into hours and minutes */
ASSIGN
iHr = INTEGER(ENTRY(1, pcTime, ":"))
iMn = INTEGER(ENTRY(2, pcTime, ":"))
NO-ERROR.
IF ERROR-STATUS:ERROR THEN RETURN.
/* Adjust the time */
CASE pcAction:
WHEN "Add" THEN iMn = iMn + 20.
WHEN "Subtract" THEN iMn = iMn - 20.
END CASE.
/* Correct for boundaries */
IF iMn > 59 THEN
ASSIGN
iMn = iMn - 60
iHr = iHr + 1.
IF iMn < 0 THEN
ASSIGN
iMn = iMn + 60
iHr = iHr - 1.
IF iHr > 23 THEN iHr = iHr - 24.
IF iHr < 0 THEN iHr = iHr + 24.
/* Build the new time string */
pcTime = STRING(iHr, "99") + ":" + STRING(iMn, "99").
END PROCEDURE.
In this example, change the cTime string to different times and run it.
The Progress TIME function returns the integer number of seconds past midnight. So your idea of converting to such an integer is consistent with other uses within the 4gl which is a positive.
Personally I would keep the UI and the internal storage independent. So I would probably have a variable for the hour, another for the minute and a 3rd for seconds (if you need that). And I would use integers, rather than characters, for all 3.
I've no idea what version of Progress or what the environment that you are running in is but this quick and dirty little snippet might have some useful tidbits:
define variable hh as integer no-undo format ">9".
define variable mm as integer no-undo format "99".
define variable ss as integer no-undo format "99".
define variable myTime as integer no-undo.
form
hh mm ss
with
frame a
.
on value-changed of hh in frame a do:
if integer( self:screen-value ) > 23 then
do:
hh = 23.
display hh with frame a.
end.
end.
on value-changed of mm in frame a do:
if integer( self:screen-value ) > 59 then
do:
mm = 59.
display mm with frame a.
end.
end.
on value-changed of ss in frame a do:
if integer( self:screen-value ) > 59 then
do:
ss = 59.
display ss with frame a.
end.
end.
update hh mm ss with frame a.
myTime = (( hh * 3600 ) + ( mm * 60 ) + ss ).
display string( myTime, "hh:mm:ss am" ).
Deriving from TheDrooper's Code I would probably write something like the following code to fully utilize the built-in functions.
Shorter code that is still at least as easy to understand is often preferable.
Also consider that Progress does not have an optimizing compiler. If you can replace replace several simple statements with less statements (even if those are more complex and powerful than needed) the code is not only more maintainable but also faster.
DEFINE VARIABLE cTime AS CHARACTER NO-UNDO.
cTime = "12:45".
RUN changeMins (INPUT-OUTPUT cTime, INPUT "Add").
MESSAGE cTime VIEW-AS ALERT-BOX INFORMATION BUTTONS OK.
PROCEDURE changeMins:
DEFINE INPUT-OUTPUT PARAMETER pcTime AS CHARACTER NO-UNDO.
DEFINE INPUT PARAMETER pcAction AS CHARACTER NO-UNDO.
DEFINE VARIABLE iMn AS INTEGER NO-UNDO.
iMn = INTEGER(ENTRY(1, pcTime, ":")) * 60
+ INTEGER(ENTRY(2, pcTime, ":"))
NO-ERROR. /* Calculate minutes since midnight */
IF ERROR-STATUS:ERROR THEN RETURN.
/* Adjust the time */
CASE pcAction:
WHEN "Add" THEN iMn = iMn + 20.
WHEN "Subtract" THEN iMn = iMn - 20.
END CASE.
/* Build the new time string */
pcTime = string(iMn * 60, 'HH:MM'). /* Convert minutes to seconds and convert the result to a string */
END PROCEDURE.
If you need to allow for higher hour values (eg. if the field doesn't represent a time of day but a time interval) you can't just convert the resulting integer with the string function. In that case you could write
pcTime = string(iMn / 60, '99') + ':' + string(iMn mod 60, '99').
(Both TheDrooper and Tom Bascom seem to assume time of day.)

Dynamic Query in OpenEdge

Good day:
Quick question: Can I perform a dynamic query in OpenEdge?
Example:
def temp-table tt-num1
field f1 as int
field f2 as int.
def temp-table tt-num2
field f1 as int
field f2 as int.
def temp-table tt-num3
field f1 as int
field f2 as int.
What I need is something that looks like this:
procedure repeat-query:
for each 'variable that contains table-name' no-lock.
disp f1 f2.
end.
end procedure.
or some other way that can solve my problem.
How do I proceed with this? I tried to check for dynamic query on the Internet but with no luck. Thanks in advance.
If you go directly to https://documentation.progress.com/#page/progdocindex%2Fopenedge.html you can find documentation around everything OpenEdge. For instance dynamic queries.
I don't understand exactly what you try to do but here's an example of a dynamic query.
DEFINE TEMP-TABLE tt-num1 NO-UNDO
FIELD f1 AS INTEGER
FIELD f2 AS INTEGER.
DEFINE TEMP-TABLE tt-num2 NO-UNDO
FIELD f1 AS INTEGER
FIELD f2 AS INTEGER.
DEFINE TEMP-TABLE tt-num3 NO-UNDO
FIELD f1 AS INTEGER
FIELD f2 AS INTEGER.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 1
tt-num1.f2 = 1.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 1
tt-num1.f2 = 2.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 2
tt-num1.f2 = 1.
CREATE tt-num1.
ASSIGN
tt-num1.f1 = 2
tt-num1.f2 = 2.
DEFINE VARIABLE hQuery AS HANDLE NO-UNDO.
DEFINE VARIABLE cBuffer AS CHARACTER NO-UNDO.
DEFINE VARIABLE cField AS CHARACTER NO-UNDO.
DEFINE VARIABLE iValue AS INTEGER NO-UNDO.
ASSIGN
cBuffer = "tt-num1"
cField = "f1"
iValue = 1.
CREATE QUERY hQuery.
hQuery:ADD-BUFFER(cBuffer).
hQuery:QUERY-PREPARE("for each " + cBuffer + " where " + cBuffer + "." + cField + " = " + STRING(iValue)).
hQuery:QUERY-OPEN().
queryLoop:
REPEAT:
hQuery:GET-NEXT().
IF hQuery:QUERY-OFF-END THEN LEAVE queryLoop.
DISPLAY hQuery:GET-BUFFER-HANDLE(1):BUFFER-FIELD(cField):BUFFER-VALUE.
END.
hQuery:QUERY-CLOSE().
DELETE OBJECT hQuery.
As Stefan Drissen mentions in a very valid comment: the loop can be more compact:
DO WHILE hQuery:GET-NEXT():
/* Code goes here */
END.

Resources