TwinCAT Systemtime-Timestruct to milliseconds since epoch - twincat

I need to convert the timestruct I get from Beckhoffs function block "FB_LocalSystemTime" to milliseconds since epoch to receive the local computer time in milliseconds.
Unfortunately I can't find a function to convert this timestruct. Any help is appreciated.
//Local Systemtime variables
fbTime : FB_LocalSystemTime := ( bEnable := TRUE, dwCycle := 1 );

You will get miliseconds with this function:
FUNCTION F_SYSTEMTIME_TO_TIMESTRUCT : TIMESTRUCT
VAR
fbGetSystemTime : GETSYSTEMTIME; (*timestamp*)
fileTime : T_FILETIME;
sDT: STRING(30);
END_VAR
fbGetSystemTime(timeLoDW => fileTime.dwLowDateTime, timeHiDW => fileTime.dwHighDateTime);
sDT := SYSTEMTIME_TO_STRING(FILETIME_TO_SYSTEMTIME(fileTime));
F_SYSTEMTIME_TO_TIMESTRUCT.wYear := STRING_TO_WORD(LEFT(sDt, 4));
F_SYSTEMTIME_TO_TIMESTRUCT.wMonth := STRING_TO_WORD(MID(sDt, 2, 6));
F_SYSTEMTIME_TO_TIMESTRUCT.wDay := STRING_TO_WORD(MID(sDt, 2, 9));
F_SYSTEMTIME_TO_TIMESTRUCT.wHour := STRING_TO_WORD(MID(sDt, 2, 12));
F_SYSTEMTIME_TO_TIMESTRUCT.wMinute := STRING_TO_WORD(MID(sDt, 2, 15));
F_SYSTEMTIME_TO_TIMESTRUCT.wSecond := STRING_TO_WORD(MID(sDt, 2, 18));
F_SYSTEMTIME_TO_TIMESTRUCT.wMilliseconds := STRING_TO_WORD(RIGHT(sDt, 3));

I think you can use DT_TO_DINT after converting the TIMESTRUCT to DT. This should give you seconds since Jan 1, 1970.
EDIT:
This code should give you milliseconds since 1/1/1970.
PROGRAM MAIN
VAR
fbTime: FB_LocalSystemTime;
tStruct: TIMESTRUCT;
msec: DINT;
dTime: DATE_AND_TIME;
eTime_sec: DINT;
eTime_msec: LINT;
END_VAR
fbTime(bEnable:=TRUE, dwCycle:=1, SystemTime=>tStruct);
msec := tStruct.wMilliseconds;
tStruct.wMilliseconds := 0;
dTime := SYSTEMTIME_TO_DT(tStruct);
eTime_sec := DT_TO_DINT(dTime);
eTime_msec := DINT_TO_LINT(eTime_sec) * 1000 + msec;

You can use the SYSTEMTIME_TO_DT() function to convert a timestruct to dt which is a 4byte DATE_AND_TIME data type.
The smallest unit of this data type is a second though and not a millisecond.
Given that TIMESTRUCT has a millisecond value in it, you can easily use it and concatenate everything to a human readable string.

I've used function GetSystemTime() which returns number of 100ns since 1 January 1601 (god knows why). So we just need to shift up to 1/1/1970 by add 11644473600_000_000_0 (which is amount od 100ns periods between dates) and then convert 100ns periods to e.g. miliseconds by divide over 1000_0 or seconds by divide them over 1_000_000_0. Remember that's a UTC time, if You want to get local time use FB_LocalSystemTime and timestruct conversion as #kolyur mentioned.
FUNCTION GET_UNIX_EPOCH : ULINT
GET_UNIX_EPOCH := (F_GetSystemTime() - 116444736000000000) / 10000;

Related

How to make inputs of a function block method optional?

When calling a method of a function block, is it possible to make certain input variables optional? If I call fbA.methA() without assignments for all input variables, TwinCAT throws an error: "Function methA requires exactly 'x' inputs." There are times when some inputs are unnecessary or irrelevant, but so far I've had to assign dummy values to those inputs to get the code to compile.
I don't think that that is possible. You could make extra methods which all call a base method.
For example:
FUNCTION_BLOCK Multiplier
METHOD Multiply : REAL
VAR_INPUT
number1 : REAL;
number2 : REAL;
END_VAR
METHOD MultiplyByTwo : REAL
VAR_INPUT
number : REAL;
END_VAR
MultiplyByTwo := Multiply(2, number);
That way you also reduce the number of inputs of your method, thereby making it easier to test and use.
You also could screen the parameters as they are passed in (still requires parameters but they have no meaning aka always pass "0").
FUNCTION_BLOCK CAT
METHOD DECIBELS: REAL
VAR_INPUT
MEOW, PURR: BOOL;
END_VAR
// body
DECIBELS := 0.0;
IF MEOW <> 0
DECIBELS := DECIBELS + 10.0;
END_IF;
IF PURR <> 0
DECIBELS := DECIBELS + 5.0;
END_IF;
END_METHOD
END_FUNCTION_BLOCK
you can invoke this like:
PROGRAM MAIN
VAR
C: CAT;
RESULT: ARRAY [1..4] OF REAL;
END_VAR
// body
RESULT[1] := C.DECIBELS(TRUE, TRUE); // will return 15.0
RESULT[2] := C.DECIBELS(TRUE, 0); // will return 10.0
RESULT[3] := C.DECIBELS(0, TRUE); // will return 5.0
RESULT[4] := C.DECIBELS(0, 0); // will return 0.0
END_PROGRAM
Hope this helps

Issue with calculation in function

I'm quite new to programming and I can't get a function to calculate properly. It is a compound interest calculator that uses this formula:
I = P ( 1 + i )n — P (p= principal i= interest n= years) Rate := to interest value.
On pascal my function looks like this,
function Compoundinterest(principal, years: integer; rate: double): double;
var
divrate: double;
interest: Double;
begin
divrate := rate/100;
interest := principal * power(1 + divrate, years) - Principal;
result := interest;
end;
It compiles fine but just wont return the right value.
for example 1000 principal, 15% interest over 3 years returns this : 1.52087500000000E+000.
I assume I'm doing something wrong in the formula?
Thanks for your help in advance.
In pascal, a function returns what it's name has been set to within the function. For example:
function set_one(): integer;
begin
set_one := 1
end;
In your function, you should replace
result := interest;
with
Compoundinterest := interest;
or to show in completion (with a few changes):
function compound_interest(principal, years: integer; rate: double): double;
var
divrate: double;
begin
divrate := rate / 100.0;
compound_interest := principal * power(1 + divrate, years) - principal;
end;
However, this assumes that you have access to the power function. In order to access the power function, the program must have: uses math written under the program header. This code was tested on compiles on Free Pascal Compiler version 2.6.4.
For more info on Pascal, see: https://www.tutorialspoint.com/pascal/pascal_functions.htm
For an online Pascal terminal, see:
https://www.tutorialspoint.com/compile_pascal_online.php
I tested here with Free Pascal 3.0.0 and it works (5.20875. I added
{$mode delphi}
uses math;
before your code and
begin
writeln(compoundinterest(1000,3,15));
end.
after. Verify that you do this too, or explain more about which pascal system you use.
If this is only a first step in some calculation you might also be interested in the math unit financial functions
You have to set the format of decimal using
:0:2
Try this
result := interest:0:2;
Counting the number of decimal places in pascal
var
divrate: double;
interest: Double;
begin
divrate := rate/100;
interest := principal * power(1 + divrate, years) - Principal;
result := interest:0:2;
end;

How to print Ada.Real_Time.Time variable

How to print Ada.Real_Time.Time variable?
procedure Main is
test : TValue :=
(value => 0.7,
timeStamp => Clock,
status => (Valid => False, Unknown => True)
);
begin
-- print of Test.value
Put(Item => Test.value ,Fore => 5, Aft => 3, Exp => 0);
-- here I want a print of timestamp
end Main;
I want to print timeStamp to console, how to do it? I tried to convert it to String or Integer, but with no success
The important section to read is LRM D.8, which defines package Ada.Real_Time. Specifically it is worth noting paragraph 19, which says that the epoch is not specified by the language. This means that for printing Ada.Real_Time.Time variables, you have to define an epoch yourself. One such epoch could be the starting time of the application you are testing:
with Ada.Real_Time;
package Real_Time_IO is
...
private
Epoch : constant Ada.Real_Time.Time := Ada.Real_Time.Clock;
end Real_Time_IO;
Now you can calculate time spans since the application started:
package body Real_Time_IO is
function Since_Start return Ada.Real_Time.Time_Span is
begin
return Ada.Real_Time.Clock - Epoch;
end Since_Start;
...
If we only need to use this package up to 24 hours after application start, we can be lazy and just convert the Since_Start result to the type Duration, and then to a string:
function Since_Start return Duration is
begin
return Ada.Real_Time.To_Duration (Since_Start);
end Since_Start;
function Since_Start return String is
begin
return Duration'Image (Since_Start);
end Since_Start;
... and I hope you know how to print strings. :-)
After some research, here is a short example of how to print a Real_Time.Time
with Ada.Real_Time; use Ada.Real_Time;
with Ada.Calendar;
with Ada.Calendar.Formatting;
with Ada.Text_IO; use Ada.Text_IO;
procedure PrintRT is
The_Clock : Ada.Real_Time.Time := Ada.Real_Time.Clock;
-- Convert to Time_Span
As_Time_Span : Ada.Real_Time.Time_Span := The_Clock - Time_Of(0, Time_Span_Zero);
-- Epoch ?
Epoch : constant Ada.Calendar.Time := Ada.Calendar.Time_Of(1970, 01, 01);
Dur : Duration := Ada.Real_Time.To_Duration(As_Time_Span);
begin
Put_Line(Ada.Calendar.Formatting.Image(Ada.Calendar."+"(Epoch, Dur)));
end PrintRT;
The only way to get a Time_Span is to use the "-" operator between two times.
As Seconds_Count represents the number of seconds from the Epoch, you can build a Time from it using Time_Of.
Then converting it to duration allows you to find the correct date.
There’s no conversion between Real_Time.Time and Calendar.Time because Real_Time is monotonically increasing (until it overflows, of course; you should be able to expect at least 50 years’ worth, ARM D.8(30)) while Calendar may be affected by daylight savings, NTP updates etc.
Would Ada.Real_Time.Delays.To_Duration help? (this is a non-standard GNAT package).
I tried
function To_Duration (T : Ada.Real_Time.Time) return Duration is
use type Ada.Real_Time.Time;
begin
return Ada.Real_Time.To_Duration (T - Ada.Real_Time.Time_First);
end To_Duration;
but it failed (Mac OS X, desktop; might well work better over an RTOS) with
raised CONSTRAINT_ERROR : a-reatim.adb:94 overflow check failed
Failing that, you could use Unchecked_Conversion to some unsigned type of the same size as Real_Time.Time.

How to get File Created, Accessed and Modified dates the same as windows properties?

I am trying to get the same Created, Accessed and Modified dates as appears in the windows properties as in:
But am finding the times are consistently 30 minutes out:
Believe it may have something to do with timezones/daylight savings but have been unable to find a solution. Have tried looking at:
TimeZone Bias and adjusting and looking at different methods including:
How to get create/last modified dates of a file in Delphi?
Current code:
var
MyFd TWin32FindData;
FName: string;
MyTime: TFileTime;
MySysTime: TSystemTime;
myDate, CreateTime, AccessTime, ModTime: TDateTime;
Begin
...
FindFirstFile(PChar(FName), MyFd);
MyTime:=MyFd.ftCreationTime;
FileTimeToSystemTime(MyTime, MySysTime);
myDate := EncodeDateTime(MySysTime.wYear, MySysTime.wMonth, MySysTime.wDay, MySysTime.wHour,
MySysTime.wMinute, MySysTime.wSecond, MySysTime.wMilliseconds);
Memo1.Lines.Add('Created: '+ FormatDateTime('dddd, d mmmm yyyy, hh:mm:ss ampm', MyDate));
...
Any help appreciated
Thanks
Paul
I'm not sure what's wrong with your current code, but I believe this code will do what you need, using standard Windows API calls.
procedure TMyForm.ReportFileTimes(const FileName: string);
procedure ReportTime(const Name: string; const FileTime: TFileTime);
var
SystemTime, LocalTime: TSystemTime;
begin
if not FileTimeToSystemTime(FileTime, SystemTime) then
RaiseLastOSError;
if not SystemTimeToTzSpecificLocalTime(nil, SystemTime, LocalTime) then
RaiseLastOSError;
Memo1.Lines.Add(Name + ': ' + DateTimeToStr(SystemTimeToDateTime(LocalTime)));
end;
var
fad: TWin32FileAttributeData;
begin
if not GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, #fad) then
RaiseLastOSError;
Memo1.Clear;
Memo1.Lines.Add(FileName);
ReportTime('Created', fad.ftCreationTime);
ReportTime('Modified', fad.ftLastWriteTime);
ReportTime('Accessed', fad.ftLastAccessTime);
end;
procedure TMyForm.Button1Click(Sender: TObject);
begin
ReportFileTimes(Edit1.Text);
end;
You should be able to use the code below to transform a UTC date time value to a local date time vale:
uses
Windows;
function UTCTimeToLocalTime(const aValue: TDateTime): TDateTime;
var
lBias: Integer;
lTZI: TTimeZoneInformation;
begin
lBias := 0;
case GetTimeZoneInformation(lTZI) of
TIME_ZONE_ID_UNKNOWN:
lBias := lTZI.Bias;
TIME_ZONE_ID_DAYLIGHT:
lBias := lTZI.Bias + lTZI.DaylightBias;
TIME_ZONE_ID_STANDARD:
lBias := lTZI.Bias + lTZI.StandardBias;
end;
// UTC = local time + bias
// bias is in number of minutes, TDateTime is in days
Result := aValue - (lBias / (24 * 60));
end;
Judging from your images your offset is actually 10 hours and 30 minutes. Are you located in South Australia?

Quick padding of a string in Delphi

I was trying to speed up a certain routine in an application, and my profiler, AQTime, identified one method in particular as a bottleneck. The method has been with us for years, and is part of a "misc"-unit:
function cwLeftPad(aString:string; aCharCount:integer; aChar:char): string;
var
i,vLength:integer;
begin
Result := aString;
vLength := Length(aString);
for I := (vLength + 1) to aCharCount do
Result := aChar + Result;
end;
In the part of the program that I'm optimizing at the moment the method was called ~35k times, and it took a stunning 56% of the execution time!
It's easy to see that it's a horrible way to left-pad a string, so I replaced it with
function cwLeftPad(const aString:string; aCharCount:integer; aChar:char): string;
begin
Result := StringOfChar(aChar, aCharCount-length(aString))+aString;
end;
which gave a significant boost. Total running time went from 10,2 sec to 5,4 sec. Awesome! But, cwLeftPad still accounts for about 13% of the total running time. Is there an easy way to optimize this method further?
Your new function involves three strings, the input, the result from StringOfChar, and the function result. One of them gets destroyed when your function returns. You could do it in two, with nothing getting destroyed or re-allocated.
Allocate a string of the total required length.
Fill the first portion of it with your padding character.
Fill the rest of it with the input string.
Here's an example:
function cwLeftPad(const aString: AnsiString; aCharCount: Integer; aChar: AnsiChar): AnsiString;
var
PadCount: Integer;
begin
PadCount := ACharCount - Length(AString);
if PadCount > 0 then begin
SetLength(Result, ACharCount);
FillChar(Result[1], PadCount, AChar);
Move(AString[1], Result[PadCount + 1], Length(AString));
end else
Result := AString;
end;
I don't know whether Delphi 2009 and later provide a double-byte Char-based equivalent of FillChar, and if they do, I don't know what it's called, so I have changed the signature of the function to explicitly use AnsiString. If you need WideString or UnicodeString, you'll have to find the FillChar replacement that handles two-byte characters. (FillChar has a confusing name as of Delphi 2009 since it doesn't handle full-sized Char values.)
Another thing to consider is whether you really need to call that function so often in the first place. The fastest code is the code that never runs.
Another thought - if this is Delphi 2009 or 2010, disable "String format checking" in Project, Options, Delphi Compiler, Compiling, Code Generation.
StringOfChar is very fast and I doubt you can improve this code a lot. Still, try this one, maybe it's faster:
function cwLeftPad(aString:string; aCharCount:integer; aChar:char): string;
var
i,vLength:integer;
origSize: integer;
begin
Result := aString;
origSize := Length(Result);
if aCharCount <= origSize then
Exit;
SetLength(Result, aCharCount);
Move(Result[1], Result[aCharCount-origSize+1], origSize * SizeOf(char));
for i := 1 to aCharCount - origSize do
Result[i] := aChar;
end;
EDIT: I did some testing and my function is slower than your improved cwLeftPad. But I found something else - there's no way your CPU needs 5 seconds to execute 35k cwLeftPad functions except if you're running on PC XT or formatting gigabyte strings.
I tested with this simple code
for i := 1 to 35000 do begin
a := 'abcd1234';
b := cwLeftPad(a, 73, '.');
end;
and I got 255 milliseconds for your original cwLeftPad, 8 milliseconds for your improved cwLeftPad and 16 milliseconds for my version.
You call StringOfChar every time now. Of course this method checks if it has something to do and jumps out if length is small enough, but maybe the call to StringOfChar is time consuming, because internally it does another call before jumping out.
So my first idea would be to jump out by myself if there is nothing to do:
function cwLeftPad(const aString: string; aCharCount: Integer; aChar: Char;): string;
var
l_restLength: Integer;
begin
Result := aString;
l_restLength := aCharCount - Length(aString);
if (l_restLength < 1) then
exit;
Result := StringOfChar(aChar, l_restLength) + aString;
end;
You can speed up this routine even more by using lookup array.
Of course it depends on your requirements. If you don't mind wasting some memory...
I guess that the function is called 35 k times but it has not 35000 different padding lengths and many different chars.
So if you know (or you are able to estimate in some quick way) the range of paddings and the padding chars you could build an two-dimensional array which include those parameters.
For the sake of simplicity I assume that you have 10 different padding lengths and you are padding with one character - '.', so in example it will be one-dimensional array.
You implement it like this:
type
TPaddingArray = array of String;
var
PaddingArray: TPaddingArray;
TestString: String;
function cwLeftPad4(const aString:string; const aCharCount:integer; const aChar:char; var anArray: TPaddingArray ): string;
begin
Result := anArray[aCharCount-length(aString)] + aString;
end;
begin
//fill up the array
SetLength(StrArray, 10);
PaddingArray[0] := '';
PaddingArray[1] := '.';
PaddingArray[2] := '..';
PaddingArray[3] := '...';
PaddingArray[4] := '....';
PaddingArray[5] := '.....';
PaddingArray[6] := '......';
PaddingArray[7] := '.......';
PaddingArray[8] := '........';
PaddingArray[9] := '.........';
//and you call it..
TestString := cwLeftPad4('Some string', 20, '.', PaddingArray);
end;
Here are benchmark results:
Time1 - oryginal cwLeftPad : 27,0043604142394 ms.
Time2 - your modyfication cwLeftPad : 9,25971967336897 ms.
Time3 - Rob Kennedy's version : 7,64538131122457 ms.
Time4 - cwLeftPad4 : 6,6417059620664 ms.
Updated benchmarks:
Time1 - oryginal cwLeftPad : 26,8360194218451 ms.
Time2 - your modyfication cwLeftPad : 9,69653117046119 ms.
Time3 - Rob Kennedy's version : 7,71149259179622 ms.
Time4 - cwLeftPad4 : 6,58248533610693 ms.
Time5 - JosephStyons's version : 8,76641780969192 ms.
The question is: is it worth the hassle?;-)
It's possible that it may be quicker to use StringOfChar to allocate an entirely new string the length of string and padding and then use move to copy the existing text over the back of it.
My thinking is that you create two new strings above (one with FillChar and one with the plus). This requires two memory allocates and constructions of the string pseudo-object. This will be slow. It may be quicker to waste a few CPU cycles doing some redundant filling to avoid the extra memory operations.
It may be even quicker if you allocated the memory space then did a FillChar and a Move, but the extra fn call may slow that down.
These things are often trial-and-error!
You can get dramatically better performance if you pre-allocate the string.
function cwLeftPadMine
{$IFDEF VER210} //delphi 2010
(aString: ansistring; aCharCount: integer; aChar: ansichar): ansistring;
{$ELSE}
(aString: string; aCharCount: integer; aChar: char): string;
{$ENDIF}
var
i,n,padCount: integer;
begin
padCount := aCharCount - Length(aString);
if padCount > 0 then begin
//go ahead and set Result to what it's final length will be
SetLength(Result,aCharCount);
//pre-fill with our pad character
FillChar(Result[1],aCharCount,aChar);
//begin after the padding should stop, and restore the original to the end
n := 1;
for i := padCount+1 to aCharCount do begin
Result[i] := aString[n];
end;
end
else begin
Result := aString;
end;
end;
And here is a template that is useful for doing comparisons:
procedure TForm1.btnPadTestClick(Sender: TObject);
const
c_EvalCount = 5000; //how many times will we run the test?
c_PadHowMany = 1000; //how many characters will we pad
c_PadChar = 'x'; //what is our pad character?
var
startTime, endTime, freq: Int64;
i: integer;
secondsTaken: double;
padIt: string;
begin
//store the input locally
padIt := edtPadInput.Text;
//display the results on the screen for reference
//(but we aren't testing performance, yet)
edtPadOutput.Text := cwLeftPad(padIt,c_PadHowMany,c_PadChar);
//get the frequency interval of the OS timer
QueryPerformanceFrequency(freq);
//get the time before our test begins
QueryPerformanceCounter(startTime);
//repeat the test as many times as we like
for i := 0 to c_EvalCount - 1 do begin
cwLeftPad(padIt,c_PadHowMany,c_PadChar);
end;
//get the time after the tests are done
QueryPerformanceCounter(endTime);
//translate internal time to # of seconds and display evals / second
secondsTaken := (endTime - startTime) / freq;
if secondsTaken > 0 then begin
ShowMessage('Eval/sec = ' + FormatFloat('#,###,###,###,##0',
(c_EvalCount/secondsTaken)));
end
else begin
ShowMessage('No time has passed');
end;
end;
Using that benchmark template, I get the following results:
The original: 5,000 / second
Your first revision: 2.4 million / second
My version: 3.9 million / second
Rob Kennedy's version: 3.9 million / second
This is my solution. I use StringOfChar instead of FillChar because it can handle unicode strings/characters:
function PadLeft(const Str: string; Ch: Char; Count: Integer): string;
begin
if Length(Str) < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[Count - Length(Str) + 1], Length(Str) * SizeOf(Char));
end
else Result := Str;
end;
function PadRight(const Str: string; Ch: Char; Count: Integer): string;
begin
if Length(Str) < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[1], Length(Str) * SizeOf(Char));
end
else Result := Str;
end;
It's a bit faster if you store the length of the original string in a variable:
function PadLeft(const Str: string; Ch: Char; Count: Integer): string;
var
Len: Integer;
begin
Len := Length(Str);
if Len < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[Count - Len + 1], Len * SizeOf(Char));
end
else Result := Str;
end;
function PadRight(const Str: string; Ch: Char; Count: Integer): string;
var
Len: Integer;
begin
Len := Length(Str);
if Len < Count then
begin
Result := StringOfChar(Ch, Count);
Move(Str[1], Result[1], Len * SizeOf(Char));
end
else Result := Str;
end;

Resources