Convert UTC string to TDatetime in Delphi - delphi

var
tm : string;
dt : tdatetime;
tm := '2009-08-21T09:11:21Z';
dt := ?
I know I can parse it manually but I wonder if there is any built-in function or Win32 API function to do this ?

I don't know why there are so many people shooting their mouth off when they don't know what they are talking about? I have to do this menial work; Is it a RAD tool? I sometimes find Delphi has a real superb architecture, though.
procedure setISOtoDateTime(strDT: string);
var
// Delphi settings save vars
ShortDF, ShortTF : string;
TS, DS : char;
// conversion vars
dd, tt, ddtt: TDateTime;
begin
// example datetime test string in ISO format
strDT := '2009-07-06T01:53:23Z';
// save Delphi settings
DS := DateSeparator;
TS := TimeSeparator;
ShortDF := ShortDateFormat;
ShortTF := ShortTimeFormat;
// set Delphi settings for string to date/time
DateSeparator := '-';
ShortDateFormat := 'yyyy-mm-dd';
TimeSeparator := ':';
ShortTimeFormat := 'hh:mm:ss';
// convert test string to datetime
try
dd := StrToDate( Copy(strDT, 1, Pos('T',strDT)-1) );
tt := StrToTime( Copy(strDT, Pos('T',strDT)+1, 8) );
ddtt := trunc(dd) + frac(tt);
except
on EConvertError do
ShowMessage('Error in converting : ' + strDT);
end;
// restore Delphi settings
DateSeparator := DS;
ShortDateFormat := ShortDF;
TimeSeparator := TS;
ShortTimeFormat := ShortTF;
// display test string
ShowMessage ( FormatDateTime('mm/dd/yyyy hh:mm:ss', ddtt) );
end;
http://coding.derkeiler.com/Archive/Delphi/comp.lang.pascal.delphi.misc/2006-08/msg00190.html

If you are using Indy 10, its StrInternetToDateTime() and GMTToLocalDateTime() functions (in the IdGlobalProtocols unit) can parse ISO-8601 formatted strings.

This looks like an internet protocol related activity, so you should have no problems in using the Win32 API for this. However note, that Windows does not correctly support conversion to/from UTC for historical dates that are more than approximately 20 years old - Windows simply doesn't have enough details in its time zone settings for that.

Related

Why can't a Variant contain a TDateTime before 100 CE?

Consider the following code:
procedure Test;
function d1: Variant;
var
DDt: TDateTime;
begin
DDt := EncodeDate(100,1,1);
Result := DDt;
end;
function d2: Variant;
var
DDt: TDateTime;
begin
DDt := EncodeDate(99,12,31);
Result := DDt;
end;
procedure Writedate(V: Variant);
begin
Writeln(string(V));
end;
var
V: Variant;
begin
V := d1;
Writedate(V);
V := d2;
Writedate(V);
end;
The first call to Writedate will succeed, and the output will be '01-01-0100'. The second call, however, will fail with an 'invalid argument' failure. Inspecting the code, you can see the Variant of the 99-12-31 date has a EVariantInvalidArgError error.
However, if I call FormatDateTime('c', TDateTime(V)) on either TDateTime, they will both succeed. In fact, at any point when the Variant contains a TDateTime, whose date is before 100 CE, the IDE will display a EVariantInvalidArgError when inspecting its value.
It seems odd that the Variant cannot handle the pre-100 CE date, when TDateTime can. Is this a bug in Delphi? I find it being right between 99 and 100 CE to be a bit suspicious.
Variant can contain any date value, as your code demonstrates (assignment V := d2; produces no error).
The error is raised during the conversion to string which the compiler delegates to the OS on Windows platforms. This fails because OLE Automation specifies midnight, 1 January 0100 as the minimum valid OLE Automation date value.

FireDac query not reading large integers correctly

I am trying to query a database using FireDac. Here is my code;
procedure TfSMSViewer.LoadSMSFromDatabase(path: AnsiString);
var
con: TFDConnection;
query: TFDQuery;
LI: TListItem;
B: Int64;
begin
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
con.LoginPrompt := False;
con.Open('DriverID=SQLite;Database=' + Path +' ;');
query.Connection := con;
query.SQL.Text := 'SELECT * FROM sms';
query.Open;
query.First;
While Not Query.EOF Do
Begin
LI := ListView1.Items.Add;
LI.Caption := inttostr(query.Fields[4].AsLargeInt); //This line
if query.FieldValues['type'] = 1 then
LI.SubItems.Add('Incoming')
else
LI.SubItems.Add('Outbound');
LI.SubItems.Add(query.FieldValues['address']);
LI.SubItems.Add(query.FieldValues['body']);
Query.Next;
End;
end;
However the line highlighted doesn't work correctly. In the database, an example value set in this column is 1418421520957 (a UNIX timestamp).
When that line of code is executed, the result is 1082313277.
The data type in the SQLite database is set to Integer. The freeware software I'm using to debug this shows the correct value. When debugging my code, the incorrect value is pulled from the database before any assignment is made.
Also some of the values populated in my TListView are negated.
Does TFDQuery not support large integers? How can I fix this?
Thanks
This is how I fixed it, with suggestions from TLama. Maybe this will be useful for someone, or my future self.
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
with query.FormatOptions do begin
OwnMapRules := True;
with MapRules.Add do begin
SourceDataType := dtInt32;
TargetDataType := dtInt64;
end;
end;
'BIGINT' for your Database column can solve this problem

Why DateTimeToMilliseconds in DateUtils.pas is marked as internal?

Why DateTimeToMilliseconds in DateUtils.pas is marked as internal?
Can I use it?
{ Internal, converts a date-time to milliseconds }
function DateTimeToMilliseconds(const ADateTime: TDateTime): Int64;
var
LTimeStamp: TTimeStamp;
begin
LTimeStamp := DateTimeToTimeStamp(ADateTime);
Result := LTimeStamp.Date;
Result := (Result * MSecsPerDay) + LTimeStamp.Time;
end;
[Delphi XE]
I have found this on About.com:
Experience shows that creating two TDateTime values using the function and EncodeDateTime that are distant from each other only a millisecond, the function returns a MillisecondsBetween not return as was expected, proving that it is not accurate.
So, if I don't care about few milisecs, I should use it.
The TDateTime is a floating point double. To minimize rounding errors when working with TDateTime values, most calculations in DateUtils converts the TDateTime to milliseconds.
Later when calculations are ready the Int64 value is converted back to a TDateTime value again.
The internal marking is to emphasize that this function is an implementation detail, not to be utilized outside the library. That is, when working with TDateTime values, use the public functions/procedures.
This is a little test of the function MilliSecondsBetween:
program TestMSecBetween;
{$APPTYPE CONSOLE}
uses
System.SysUtils,System.DateUtils;
var
d1,d2 : TDateTime;
i,iSec,iMin,iHour,iMSec;
isb : Int64;
begin
d1 := EncodeDateTime(2013,6,14,0,0,0,0);
for i := 0 to 1000*60*60*24-1 do
begin
iHour := (i div (1000*60*60)) mod 24;
iMin := (i div (1000*60)) mod 60;
iSec := (i div 1000) mod 60;
iMSec := i mod 1000;
d2 := EncodeDateTime(2013,6,14,iHour,iMin,iSec,iMSec);
isb := MilliSecondsBetween(d2,d1);
if (isb <> i) then
WriteLn(i:10,iHour:3,iMin:3,iSec:3,iMSec:4,isb:3);
end;
ReadLn;
end.
You can expand the test for more than one day to see if there are some anomalies.
There's no reason you could not use it, it is not deprecated and used internally.
It's just marked as 'internal' because the function header is not in the interface section. If you copy the header there it should work.
What we always do if we 'patch' a third-party unit like this, is copying it to a directory in our own search path (named PatchLibs) before modifying. That way you can't 'damage' the original file and you don't have to worry about how to rebuild the original units.

Delphi stack misalignment + com marshalling = wrong marshalling

This is not exactly a straight-out question because I have just solved it, but more like "am I getting it right" type of question and a reminder for those who might get stuck into that.
Turns out, Delphi does not align variables on stack and there are no directives/options to control this behavior. Default COM marshaller on my XP SP3 seem to require 4-byte alignment when marshaling records though. Worse, when it encounters unaligned pointer, it does not return an error, oh no: it rounds the pointer down to the nearest 4-byte boundary and continues like that.
Therefore, if you pass a record you have allocated on stack into COM-marshaled function by reference, you're screwed and you won't even know.
The problem can be solved by using New/Dispose to allocate records, as memory managers tend to align everything at 8 bytes or better, but god, this is annoying, both the misalignment part and the "trim-down-pointers" part.
Is this really the reason, or am I wrong somewhere?
Update: How to reproduce (Delphi 2007 for Win32).
uses SysUtils;
type
TRec = packed record
a, b, c, d, e: int64;
end;
TDummy = class
protected
procedure Proc(param1: integer);
end;
procedure TDummy.Proc(param1: integer);
var a, b, c: byte;
rec: TRec;
begin
a := 5;
b := 9;
c := 100;
rec.a := param1;
rec.b := a;
rec.c := b;
rec.d := c;
writeln(IntToHex(integer(#rec), 8));
readln;
end;
var Obj: TDummy;
begin
obj := TDummy.Create;
try
obj.Proc(0);
finally
FreeAndNil(obj);
end;
end.
This gives odd result address, clearly not aligned on anything. If it doesn't, try adding more byte variables to "a, b, c: byte" (and don't forget to simulate some work with them at the end of the function).
The part with COM is easier to reproduce but longer to explain. Create a new VCL app called Sample Server, add a COM object SampleObject implementing ISampleObject, with a type library, free-threaded, single instance (make sure to check ISampleObject is marked as Ole Automation in type library). Open the type library, declare a new SampleRecord with five __int64 fields. Add a SampleFunction with a single SampleRecord* out parameter to ISampleObject. Implement SampleFunction in TSampleObject by returning fixed values:
function TSampleObject.SampleFunction(out rec: SampleRecord): HResult;
begin
rec.a := 1291;
rec.b := 742310;
//...
Result := S_OK;
end;
Note how Delphi declares SampleRecord as "packed record" in automatically generated type library header code:
SampleRecord = packed record
a: Int64;
b: Int64;
//...
end;
I have checked, and this, at least, was fixed in Delphi 2010. Automatically generated records are not packed there.
Register the COM server. Run it.
Now modify the source above (sample 1) to call this server instead of just doing writeln:
uses SysUtils, Windows, ActiveX, SampleServer_TLB;
procedure TDummy.Proc(param1: integer);
var a, b, c: byte;
rec: SampleRecord;
Server: ISampleObject;
begin
a := 5;
b := 9;
c := 100;
rec.a := param1;
rec.b := a;
rec.c := b;
rec.d := c;
Server := CoSampleObject.Create;
hr := Server.SampleFunction(rec);
writeln('#: 'IntToHex(integer(#rec), 8)+', rec.a='+IntToStr(rec.a));
readln;
end;
var Obj: TDummy;
begin
CoInitializeEx(nil, COINIT_MULTITHREADED);
obj := TDummy.Create;
try
obj.Proc(0);
finally
FreeAndNil(obj);
CoUninitialize();
end;
end.
Observe that when the address of rec is not aligned, values of rec fields are wrong (specifically, bitshifted to 8, 16 or 24 bits, sometimes wrapped over to the next value).
Do you have an example of the stack being misaligned? Delphi should be aligning everything to 4 byte boundaries. The only case I can think of that would cause misalignment is if someplace along the call-chain is some assembler code that has explicitly done something to the stack to mis-align it.

How to convert int to currency?

I'm working with Delphi 2009,I binged my question,but the answers I've gotten are outdated since It doesn't recognise StrtoFloat in Delphi2009.
I'm asking how to convert an integer ,for example, '1900000' to '1,900,000'?
You can also use the format command. Because the format expects a real number, adding 0.0 to the integer effectively turns it into an extended type.
Result := Format('%.0m',[intValue + 0.0]));
This handles negative numbers properly and adds the currency symbol for the users locale. If the currency symbol is not wanted, then set CurrencyString := ''; before the call, and restore it afterwards.
SavedCurrency := CurrencyString;
try
CurrencyString := '';
Result := Format('%.0m',[intValue + 0.0]));
finally
CurrencyString := SavedCurrency;
end;
To force commas, just set the ThousandSeparator := ',';
CurrencyString := '!';
ThousandSeparator := '*';
Result := Format('%.0m',[-1900000.0]);
// Returns (!1*900*000) in my locale.
The "period" in the mask determines how the fractional portion of the float will display. Since I passed 0 afterwards, it is telling the format command to not include any fractional pieces. a format command of Format('%.3m',[4.0]) would return $4.000.
I currently use this :
function FloatToCurrency(const f: double): string;
begin
Result := FormatFloat('#,###.##;1;0', f);
end;
It doesn't work with negative numbers, but since you need currency you won't have that problem.
You can assign Integer to Currency directly by assignment, the compiler will do the conversion for you:
var
Int : Integer;
Cur : Currency;
begin
Int := 1900000;
Cur := Int;
ShowMessage(CurrToStr(Cur)); // 1900000
ShowMessage(Format('%m', [Cur]); // 1,900,000.00 in US/UK/NZ/AU etc, "1 900 000,00" in Spain etc.
ShowMessage(Format('%.0m', [Cur]); // 1,900,000 in US/UK/NZ/AU etc, "1 900 000" in Spain etc.
end;
If you want Commas using Spanish regional settings set ThousandSeparator := ','; or use the extended CurrToStrF(amount, ffCurrency, decimals, FormatSettings)) version.
The verison with FormatSettings is also thread-safe.
Note: You can't assign Currency to Integer directly, You would need to use Int := Trunc(Cur) but this is inefficient as it converts to float first (unless compiler does something smart).
wouldnt this be more of a format thing, delphi should have some type of support for formating the number into a string the way you want right? Besides isnt the newer versions of delphi more aligned with the .net framework?

Resources