How to convert UTC to local time - delphi

I have to develop an application for ePrescribe and need to convert an UTC Time value (for example '2010-01-01T16:09:04.5Z') to local time. Delphi 2010, any suggestions?

You have to parse the string manually first. Extract the individual values from it, then you can put them into a Win32 SYSTEMTIME record and call SystemTimeToTzSpecificLocalTime() to convert it from UTC to local. You can then use the converted SYSTEMTIME however you need, such as converting it to a TDateTime using SystemTimeToDateTime().

You could use TXSDateTime class from unit XSBuiltIns
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
XSBuiltIns;
var xsDateTime: TXSDateTime;
input, output: string;
date: TDateTime;
begin
try
{ TODO -oUser -cConsole Main : Insert code here }
input := '2010-01-01T16:09:04.5Z';
xsDateTime := TXSDateTime.Create;
xsDateTime.XSToNative(input);
date := xsDateTime.AsDateTime;
output := 'Parsed date/time: ' + FormatDateTime('yyyy-mm-dd hh:nn:ss', date);
writeln(output);
readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Output:
Parsed date/time: 2010-01-01 19:09:04

Your computer provides your time zone. You can use them to manipulate UTC time to adjust by adding hours and minutes.

Related

Date format in RecordSet

I need to store a date in a Recordset. I am doing it as follows:
rs: _Recordset;
dt: TDatetime;
Rs := CoRecordset.Create;
Rs.Fields.Append('Date', adDate, 4, adfldupdatable, Unassigned);
Rs.Fields.Item['Date'].Value := FormatDateTime('dd/mm/yyyy', TDatetime)
But, in the Recordset the date appears in format d/m/yyyy
How can I store dd/mm/yyyy in the recordset?
The adDate is not a string format, it is a date format. When you set value, it is performing a conversion from the string you entered into its internal format (which renders as d/m/yyyy when saved to a file).
If you want a specific string format for your date, use a string field to store the date. The disadvantage is that you are going to then need to make sure that you process the string field properly if you change your region settings to one where the date format is in M/D/Y order instead of what you stored as D/M/Y order.
I'm afraid I cannot get your code to function as you claim. In any case, it is clearly in error
because the line
Rs.Fields.Item['Date'].Value := FormatDateTime('dd/mm/yyyy', TDatetime)
is wrong, because you are passing TDateTime as the second argument, not your
dt variable you apparently intend.
So, instead, using Delphi 10.4.2. I have compiled and run this program on Win10
program RecordSetTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Variants, System.Win.ComObj, WinAPI.ActiveX, WinAPI.AdoInt;
var
RS: _Recordset;
DT: TDatetime;
begin
CoInitialize(Nil);
DT := Now;
RS:= CoRecordset.Create;
RS.Fields.Append('Date', adDate, 4, adfldupdatable, Unassigned);
RS.Open('c:\temp\rsdata.xml', EmptyParam, adOpenStatic, adLockOptimistic, 0);
RS.AddNew('Date', FormatDateTime('dd/MM/yyyy', DT));
RS.Save('c:\temp\rsdata.xml', adPersistXML);
RS := Nil;
writeln('saved');
readln;
end.
Note the call to AddNew, which is necessary to add a data record to the RecordSet file.
On my system, which has short and long date formats of dd/MM/yyyy and dd MMMM yyyy, the file
written is as follows:
<xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'
xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'
xmlns:rs='urn:schemas-microsoft-com:rowset'
xmlns:z='#RowsetSchema'>
<s:Schema id='RowsetSchema'>
<s:ElementType name='row' content='eltOnly' rs:updatable='true'>
<s:AttributeType name='Date' rs:number='1' rs:write='true'>
<s:datatype dt:type='dateTime' rs:dbtype='variantdate' dt:maxLength='16' rs:precision='0' rs:fixedlength='true'
rs:maybenull='false'/>
</s:AttributeType>
<s:extends type='rs:rowbase'/>
</s:ElementType>
</s:Schema>
<rs:data>
<z:row Date='2021-10-20T12:32:59'/>
</rs:data>
</xml>
Note the format of the date value
2021-10-20T12:32:59
despite the formatting of the date value in
RS.AddNew('Date', FormatDateTime('dd/MM/yyyy', DT));

TXSDateTime error "local time situated within the missing period prior to DST'

We convert our local dates (no time parts) to a external system expecting UTC datetime strings, by adding TTimeZone.Local.UTCOffset (now 2 hours) to a TDateTime.
This fails for the night we switch to DST (at 02:00).
Error from System.RTLConst:
SLocalTimeInvalid = 'The given "%s" local time is invalid (situated within the missing period prior to DST).';
occurring in System.DateUtils:
function TTimeZone.GetUtcOffsetInSeconds(const ADateTime: TDateTime; const ForceDaylight: Boolean): Int64;
var
LOffset, LDSTSave: Int64;
LType: TLocalTimeType;
begin
{ Obtain the information we require }
DoGetOffsetsAndType(ADateTime, LOffset, LDSTSave, LType);
{ Select the proper offset }
if (LType = lttInvalid) then
raise ELocalTimeInvalid.CreateResFmt(#SLocalTimeInvalid, [DateTimeToStr(ADateTime)])
else if (LType = lttDaylight) or ((LType = lttAmbiguous) and ForceDaylight) then
Result := LOffset + LDSTSave
else
Result := LOffset;
end;
Code to reproduce:
function DateTime2UTCString(ADateTime: TDateTime): String;
var XSD: TXSDateTime;
begin
XSD := TXSDateTime.Create;
try
try
XSD.AsDateTime := ADateTime;
Result := XSD.NativeToXS;
except
on E:Exception do
Result := E.Message;
end;
finally
XSD.Free;
end;
end;
function Date2UTCString(ADateTime: TDateTime): String;
// Input is guaranteed to have no time fraction
begin
ADateTime := ADateTime + TTimeZone.Local.UTCOffset;
Result := DateTime2UTCString(ADateTime);
end;
procedure TFrmUTCandDST.Button1Click(Sender: TObject);
var
lDT: TDateTime;
l : integer;
begin
lDT := EncodeDate(2016,3,25);
for l := 0 to 2 do
begin
lDT := lDT +1;
Memo1.Lines.Add(DateToStr(lDT) + ' -> ' + Date2UTCString(lDT));
end;
end;
(Don't forget to use SOAP.XSBuiltIns, System.DateUtils, System.TimeSpan).
Output:
26-3-2016 -> 2016-03-26T02:00:00.000+01:00
27-3-2016 -> The given "27-3-2016 2:00:00" local time is invalid (situated within the missing period prior to DST).
28-3-2016 -> 2016-03-28T02:00:00.000+02:00
How can I graciously circumvent this? I can use TTimeZone.Local.IsInvalidTime(ADateTime) to detect invalid dates, but
26-3-2016 2:00:00 would be wrong (that's exactly the time we moved to DST), not 27-3-2016 2:00:00 - so I don't know how to adjust in case of the 'invalid' date.
There is bug in unit System.DateUtils.pas (afaik still present in 10.1).
Function AdjustDateTime first takes date and time handling it as local time, and THEN tries to put offset into it. Since during daylight saving time there is a "missing hour" (in case of central Europe it was 26.03.2017), therefore after 1:59:59 A.M you've got 3:00:00 A.M.
If you accidentally use this period (like 2:17:35), you'll get an exception.
This is also present in other functions.
Simple code to reproduce the exception (C++):
ShowMessage(ISO8601ToDate("2017-03-26T02:22:50.000Z",false));
but this one runs ok:
ShowMessage(ISO8601ToDate("2017-03-26T02:22:50.000Z",true));`
For now to avoid the exception use XSD.AsUTCDateTime, then apply the local offset.
Example in c++ :
TTimeZone * localTz = TTimeZone::Local;
TDateTime TimeSomething = localTz->ToLocalTime(XSD->AsUTCDateTime);
In your case either local time is indeed invalid (there was no "2:00"),
or somewhere you're trying to treat UTC Time as local time, which of course is invalid. Solve this and you will solve your problem.
How can I graciously circumvent this? I can use TTimeZone.Local.IsInvalidTime(ADateTime) to detect invalid dates, but 26-3-2016 2:00:00 would be wrong (that's exactly the time we moved to DST), not 27-3-2016 2:00:00 - so I don't know how to adjust in case of the 'invalid' date.
Additionally i think you missing that in year 2016 we moved to DST at 27.03 at 2:00 but THIS year at 26-03, so 27-3-2016 2:00:00 is perfectly invalid date :)
As #Vancalar said, there is a bug in ISO8601ToDate when converting from UTC to local time near a DST transition. This bug persists in Rio 10.3.3.
A simple workaround is to avoid ISO8601ToDate's timezone conversion and let Microsoft do it. That is, replace the false value with true and follow with a call to UTCToTZLocalTime, as in the following Pascal snippet:
// Get UTC datetime from ISO8601 string
datetime := ISO8601ToDate(ISO8601UTCstring, true);
// Convert to local time w/DST conversion
datetime := UTCToTZLocalTime(zoneinfo,datetime);

Delphi: DateTimeToStr output with zero time (midnight)

I have found a similar question here, but it is unrelated to what I am trying to do. I have done a lot of research on the Internet and I have determined that Delphi is working as designed or intended, where it omits the time if the time is zero. I have an application which displays the date & time in a listview, and when the time is midnight, it doesn't show 00:00:00, and therefore making the results look uneven and out of place.
The way I've gotten around this which is still locale independant is to add a microsecond to the time, see sample code:
program Test11;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, Winapi.Windows;
begin
try
Writeln(DateTimeToStr(44167, TFormatSettings.Create(GetThreadLocale)));
Writeln(DateTimeToStr(44167.00000001, TFormatSettings.Create(GetThreadLocale)));
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
And subsequent output:
02/12/2020
02/12/2020 00:00:00
The question is - is there a better, more programatically correct way to do achieve this?
Running Delphi XE6
You can use FormatDateTime function for more control over date time formatting.
FormatDateTime('ddddd tt', 44167, TFormatSettings.Create);
Note: There is no need to call TFormatSettings.Create(GetThreadLocale) with locale parameter because plain TFormattSettings.Create call will internally use GetThreadLocale on Windows platform.
This is "normal" behavior of VCL. Look at the related code from System.SysUtils (it is from Seattle, but I think it hasn't much changes since XE6).
function DateTimeToStr(const DateTime: TDateTime;
const AFormatSettings: TFormatSettings): string;
begin
DateTimeToString(Result, '', DateTime, AFormatSettings);
end;
procedure DateTimeToString(var Result: string; const Format: string;
DateTime: TDateTime; const AFormatSettings: TFormatSettings);
begin
...
if Format <> '' then AppendFormat(Pointer(Format)) else AppendFormat('C');
...
//This is another related part of DateTimeToString.
case Token of
'C':
begin
GetCount;
AppendFormat(Pointer(AFormatSettings.ShortDateFormat));
GetTime;
if (Hour <> 0) or (Min <> 0) or (Sec <> 0) or (MSec <> 0) then
begin
AppendChars(' ', 1);
AppendFormat(Pointer(AFormatSettings.LongTimeFormat));
end;
end;
If time part of datetime value equals to zero, then only date will be in result string.
If you want always show datetime in single format, better use FormatDateTime as suggested by other answers.

Is DateTimeToString in Delphi XE5 doesn't work?

I have small piece of code:
DateTimeToString(DiffString, 't.zzz', TDT);
ShowMessage('TDT: ' + DateTimeToStr(TDT));
ShowMessage('DiffString: ' + DiffString);
which result with first ShowMessage gives random nice DateTime TDT value...
second where DiffString is exacly 00:00.000
Could anyone check it in other IDE?
In fact DateTimeToString works just fine and is behaving exactly as designed. It is doing precisely what you asked it to.
Here is the SSCCE that you should have provided:
{$APPTYPE CONSOLE}
uses
SysUtils;
var
DiffString: string;
TDT: TDateTime;
begin
TDT := Date;
DateTimeToString(DiffString, 't.zzz', TDT);
Writeln('TDT: ' + DateTimeToStr(TDT));
Writeln('DiffString: ' + DiffString);
end.
Output:
TDT: 04/02/2014
DiffString: 00:00.000
The reason is, and I am guessing here, that your date time comes from a call to Date. Or perhaps your date time is an uninitialized variable.
Whichever way, it is clear that the time part is zero. Into DiffString you put the time and not the date. That is what the t.zzz format string means.
Try again with a date time containing a non-zero time:
{$APPTYPE CONSOLE}
uses
SysUtils;
var
DiffString: string;
TDT: TDateTime;
begin
TDT := Now;
DateTimeToString(DiffString, 't.zzz', TDT);
Writeln('TDT: ' + DateTimeToStr(TDT));
Writeln('DiffString: ' + DiffString);
end.
Output
TDT: 04/02/2014 11:16:43
DiffString: 11:16.942
Of course, t.zzz is a bad choice of format. It combines the short time format with milliseconds. As you can see, on my machine, the default short time format omits seconds. So you get hours, minutes and milliseconds. You'll need to re-think your format string. Perhaps 'hh:nn:ss.zzz' is what you need.

Convert UTC string to TDatetime in 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.

Resources