Delphi.
I need to convert a datetime string to a TDateTime type. The code I use:
...
var
fs : TFormatSettings;
dt: TDateTime;
begin
fs := TFormatSettings.Create;
fs.DateSeparator := '-';
fs.TimeSeparator := ':';
fs.ShortDateFormat := 'dd-mmm-yy';
fs.ShortTimeFormat := 'hh:nn:ss';
dt := StrToDateTime(Timestamp, fs);
...
The string is like this: Timestamp := '26-Feb-16 08:30:00'
I get only convert error messages
EConvertError, '26-Feb-16 08:30:28' is not a valid date and time
If I manually enter a timestamp of format 'yyyy/mm/dd hh:nn:ss' and make ShortDateFormat := 'yyyy/mm/dd'; and ShortTimeFormat := 'hh:nn:ss'; I have no problems...
I don't know what I'm missing? Anyone have a clue?
StrToDateTime does not support formats that specify the month as a name, either short or long form.
You will have to parse your text using some other method. Frankly, this format is very easy to parse.
Split the input on the space character to get date and time parts.
Split the date part on - to get day, month and year parts. Search through the 12 short month names to find a match.
Split the time part on : to find hours, minutes and seconds. Or use StrToTime.
Or you could take the input and replace short month names with month numbers and use StrToDateTime with the mm month format.
Related
I'm using two Month calendar on my form. I select a date on each one and I want to calculate the number of days between these two days. I only code I have is
procedure TForm1.Button1Click(Sender: TObject);
Var
N, m: TDate;
d: Real;
l,k:String;
begin
N := (MonthCalendar1.Date);
m := MonthCalendar2.Date;
L := formatdatetime('dd', N);
K:=formatdatetime('dd',M);
d := StrToFloat(L)-StrToFloat(K);
ShowMessage(FloatToStr(d));
end;
You can use DaysBetween() from System.DateUtils to obtain the difference, in days, between two TDateTime values.
See Docs: http://docwiki.embarcadero.com/Libraries/Rio/en/System.DateUtils.DaysBetween
Edit, see code:
uses System.DateUtils;
[..]
procedure TForm1.Button1Click(Sender: TObject);
var
NumDays: Integer;
begin
NumDays := DaysBetween(MonthCalendar1.Date, MonthCalendar2.Date);
ShowMessage('Days between selected dates: ' + NumDays.ToString);
end;
You don't need a MonthCalendar to do this. The part of a TDateTime to the RHS of the decimal point is effectively a "day number" so if you substract one of those from another, you find the "days between". Apply a Trunc to both to discard the time-of-day parts. See http://docwiki.embarcadero.com/Libraries/Rio/en/System.TDateTime for definition of TDateTime.
So, for example, given
var
D1,
D2 : TDateTime;
DaysBetween : Integer;
and D1 and D2 are two TDateTimes (that you could enter if you wish using a TMonthCalendar), then
DaysBetween := Trunc(D2) - Trunc(D1);
The call to the built-in Trunc function discards the fractional, time-of-day part of a TDateTime value (that is, the partr to the right of the decimal point), so that 23:59 on one day and 00:01 on the next are calculated to be one day apart. This may, or may not, be the result you want, depending on your application, which is why I have suggested calculating it yourself rather than using the built-in DaysBetween function.
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);
iAge := 2013 - StrToInt(sJ) ;
if iAge< 18
then
begin
bDatum := False ;
ShowMessage('You must be older than 18!') ;
Exit ;
end; //IF
If you use this it will just take the current year and the year the user typed in and test if he is 18 or not, I'm looking for a way to to calculate the age of the user using the month and day as well but it was to no avail, so I was hoping to get some help from Stackoverflow.
Help will be much appreciated!
The simplest way to think about this is that if you know the date when the person was born, you simply need to work out whether or not their 18th birthday has passed.
Ask the user for their date of birth. Get that in the form of day, month and year.
Add 18 to the year.
Convert that into a date with EncodeDate.
Compare that to today's date, which can be found by calling Date.
The code would look like this:
if EncodeDate(dobYear + 18, dobMonth, dobDay) > Date then
ShowMessage('Too young');
Now, this almost works, but it will fail if the person was born on a leap day, that is the 29th February. You'd need to add a special case to handle that. For example, a crude approach would be like this:
if (dobMonth=2) and (dobDay=29) then
dobDay := 28;
if EncodeDate(dobYear + 18, dobMonth, dobDay) > Date then
ShowMessage('Too young');
Looks like I've just re-invented the wheel here. Always a bad idea. You can call IncYear from DateUtils to get this done, and not have to worry about leap days.
if IncYear(EncodeDate(dobYear, dobMonth, dobDay), 18) > Date then
ShowMessage('Too young');
Delphi stores the date as a real number - you must use the Extended type
function Age(TheDate: TDate): integer;
var
I: Extended; // Extended is a special type of real variable
begin
I := Now() - TheDate; // Now() is todays date in TDate format
// The type conflict is apparently ignored
Result := round(I/365.25);
If Result > 110 then Result := 0; // this copes with a missing date string
end; // Start date in Delphi is 30/12/1899
{============================================================}
I think this is simpler:
isUnder18 := YearsBetween(DOB, Now()) < 18;
YearsBetween
i have create a coding to set a 20 millisecond faster than windows system. i'm using encodetime.
here's the code
procedure TForm1.Button1Click(Sender: TObject);
Var
delphi_datetime :tDateTime;
t_date : tdatetime ;
windows_datetime : tSystemTime;
begin
t_date := dATE;
delphi_datetime := encodetime(8,44,59,980);
delphi_datetime := incmillisecond(delphi_datetime, 20);
replacedate(t_date , delphi_datetime);
datetimetosystemtime( delphi_datetime , windows_datetime );
setlocaltime( windows_datetime );
showmessage('time now = ' + timetostr(delphi_datetime));
end;
aftr i run it, show the correct time. but the date goes to 30 dec 1899.. but i want to the current today date but with the time faster 20 milliseconds. any help.. please...
You have the arguments of ReplaceDate backward. It reads the date of the second parameter and assigns the date portion of the first parameter. The date portion of delphi_datetime is 0 because that's how EncodeTime works. You take that zero value and assign it to t_date, but then you continue working with delphi_datetime.
Reverse the arguments of ReplaceDate, and you should see that your current system time gets set to 8:45:00.000 with the current date.
ReplaceDate(delphi_datetime, t_date);
You could have noticed the mistake sooner if you hadn't used a separate t_date variable. If you'd called Date directly, your code would have failed to compile:
ReplaceDate(Date, delphi_datetime); // can't pass function result as "var" parameter
This works:
ReplaceDate(delphi_datetime, Date);
Rob has identified problems with your existing code. However your existing approach is needlessly complex. If you chose a simpler approach you would find it easier to get the code right.
If you want a date time that is 20 milliseconds greater than now, do it like this:
MyDateTime := IncMillisecond(Now, 20);
If you want a date time representing 0845 today, then you write:
MyDateTime := Date + EncodeTime(8, 45, 0, 0);
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?