I want to convert the system date time to a specific format. My system format is dd/mm/yy which i wanted to convert to mm/dd/yyyy and so i am using StrToDateDef. I need to use StrToDateDef only because the date comes as string and if there is a string other than date i will use default date. My code is below
str := '30/01/14';
GetLocaleFormatSettings(LOCALE_SYSTEM_DEFAULT, FmtStngs);
FmtStngs.DateSeparator := '/';
FmtStngs.ShortDateFormat := 'mm/dd/yyyy';
FmtStngs.TimeSeparator := ':';
FmtStngs.LongTimeFormat := 'hh:nn';
date := StrToDateDef(str,01/28/2013,FmtStngs);
I am expecting the date to be of '01/30/2014' but it is coming as '30/01/14'. What is that i am doing wrong?
There are several bugs in this code:
First the TFormatSettings you are passing to the StrToDateDef routine are the format settings for the string you are passing (and not for the datetime variable that comes out, more on that later).
As you are passing '30/01/14' your ShortDateFormat should be 'dd/mm/yyyy' and not 'mm/dd/yyyy'
Then the default value you are passing equals to like 1ms after midnight of 30.12.1899 (because you actually are passing 1 divided by 28 divided by 2014). Use EncodeDate(2013, 1, 28) from DateUtils.pas.
Then you are saying
I am expecting the date to be of '01/30/2014' but it is coming as '30/01/14'.
Well you are looking at a TDateTime variable and it will be formatted according to your local settings of your windows system by the debugger. Nothing more. You don't have a string but a float value (which is what TDateTime is) that is presented as string to you to make it readable.
Also I think the result should be the passed default date because the passed ShortDateFormat does not match the value of the string you are passing (trying to put 30 into the month part).
Related
I'm trying to automate some of my recurring tasks. Amongst other jobs, i want to automate the LDAP Account Creation. Since we have employes with fixed-term employment contract, i want to automatically set the deactivation Date of these LDAP-Accounts to the last Day working.
Our tool where I do the automation supports DelphiScript, VBScript and JavaScript. Additionaly it would support PowerShell scripts.
I have a Date variable which i could declare as DateTime or as String.
As DateTime it will look like 44366,3996712847 converted as a String it will look like 19.06.2021 09:36:35. In the end I need to convert one of these values as a 18 digit timestamp, so i can set a Account Expiration Date in LDAP.
A few years ago I did a lot in C#, but nothing with Delphi, JScript and VBScript. I'm also out of C# by now.
My approaches, where I first tried to Convert String to DateTime, look like this:
uses
Classes, SysUtils;
var
befristet: datetime;
timestamp: string;
begin
befristet := GetVarContent('DATA.Befristet');
timestamp := DateTimeToTimeStamp(befristet);
// Writing to Log
LogMessage('VarContent: ' + befristet);
// Returns 0 as script result
Result := 0;
end;
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.
I am writing column and cell classes for FMX TGrid that will contain TCalendarEdit and TTimeEdit instances in every cell. Everything works fine except the proper processing of changes done in these child controls.
type
TFMTValue<T> = record
FieldValue: T;
Modified: boolean;
Appended: boolean;
Deleted: boolean;
end;
TDateTimeCell = class(TStyledControl)
private
FDate_Time: TFMTValue<TDateTime>;
procedure SetDateTime(const Value: TFMTValue<TDateTime>);
function GetDateTime: TFMTValue<TDateTime>;
protected
procedure SetData(const Value: TValue); override;
public
property Date_Time: TFMTValue<TDateTime> read GetDateTime write SetDateTime;
...
end;
...
function TDateTimeCell.GetDateTime: TFMTValue<TDateTime>;
begin
FDate_Time.Modified := (FDate_Time.Modified) or
(FDate_Time.FieldValue <> FCalendarEdit.Date +
+ FTimeEdit.Time);
FDate_Time.FieldValue := FCalendarEdit.Date + FTimeEdit.Time;
Result := FDate_Time;
end;
procedure TDateTimeCell.SetData(const Value: TValue);
begin
Date_Time := Value.AsType<TFMTValue<TDateTime>>;
inherited SetData(TValue.From<TDateTime>(FDate_Time.FieldValue));
ApplyStyling;
end;
procedure TDateTimeCell.SetDateTime(const Value: TFMTValue<TDateTime>);
begin
FDate_Time := Value;
FCalendarEdit.Date := DateOf(FDate_Time.FieldValue);
FTimeEdit.Time := TimeOF(FDate_Time.FieldValue);
FDate_Time.FieldValue:=FCalendarEdit.Date + FTimeEdit.Time; //this line helps but not in all cases
end;
The idea is that data is assigned via TGrid OnGetValue event handler. Both date and time are displayed. The user activity is catched and Modified flag is set. The problem is that this flag is set to true sometimes even without any user activities. I suspect it is due to the rounding of time part of TDateTime. There are no other ways the code assignes values to FCalendarEdit.Date and FTimeEdit.Time.
How can I properly compare the data stored in FCalendarEdit.Date and FTimeEdit.Time with that stored in FDate_Time.FieldValue?
Appended
Setting the flag in this way does not resolve the issue.
FDate_Time.Modified := (FDate_Time.Modified) or
(DateOf(FDate_Time.FieldValue) <> FCalendarEdit.Date) or
(TimeOf(FDate_Time.FieldValue)<> FTimeEdit.Time);
Appended 2. On a valued advice of #Ken-White.
If we replace the comparison line by
FDate_Time.Modified := (FDate_Time.Modified) or
(not SameDateTime(FDate_Time.FieldValue,
FCalendarEdit.Date + FTimeEdit.Time));
It works fine. So the TDataTime comparison must be done by this function only.
TDateTime is of type Double, which means it's a floating point value, and therefore is subject to the usual issues of binary representation when doing comparisons for equality without specifying an acceptable delta (difference)..
Specifically for TDateTime values, you can use DateUtils.SameDateTime to compare equality down to less than one millisecond:
FDate_Time.Modified := (FDate_Time.Modified) or
(not SameDateTime(FDate_Time.FieldValue,
FCalendarEdit.Date + FTimeEdit.Time));
There is a bug in TCalendarEdit (a few actually) which is the underlying cause of your problem, but you can fix it with only a small change to your code.
The Problem
The TCalendarEdit makes a number of crucial errors when it applies a new Date value.
A TDate type is actually just an ordinary TDateTime in which you are supposed to ignore the time portion. Similarly a TTime is a TDateTime where you are supposed to ignore the date portion.
But you have to use these types correctly in your code - there is nothing that magically makes a TTime ignore the date or a TDate ignore the time.
For example, if you examine the constructor of the TCalendarEdit, you will see that it initialises the internal date/time to the current system date and time using Now, but truncates this to eliminate the time element:
Date := Trunc(Now);
So far so good.
But when you apply a new value via the Date property, it performs the following (simplified):
if Date <> Value then
FDateTime := Value + Time;
Both of these lines of code contain serious bugs:
It compares the Date (property returning the Date value of the control) with the Value being assigned - including any time value in that date/time. It should instead compare only the date part of the Value.
When assigning the new value to the internal date/time it adds Time to the Value you specified.
The first bug results in unnecessary changes to the internal property but is otherwise relatively innocuous. The second bug however is far more serious and is what is causing your problem.
I presume that the intention of the author of the control was to leave the time portion of the internal date/time value unchanged. However, the Value is not truncated, so it retains the time value specified in the assignment to the property. To make matters even worse, there is no Time property on this control, so this in fact adds the current system time to whatever time is specified in Value.
How This Affects Your Code and Test Case
Since your test case involved a time of midday - 12 hours - the result is that when you run this code in the afternoon, the Date of your TCalendarEdit is actually set to 25-Sep-2015 + 12 hours + the time when the control was initialised.
If you run the code in the morning, it seems to work because the time added results in a value that is still on the 25th Sep.
But when you run the code in the afternoon, the 12 hours are added to the current time and so the date rolls over to the next day!
With a more helpful diagnostic error message, or if you had inspected the properties in your code via the debugger, you would have seen this occurring.
DT := EncodeDate(2015, 9, 25) + EncodeTime(12, 0, 0, 0);
CalendarEdit1.Date := DT;
ShowMessage(DateTimeToString(CalendarEdit1.Date));
// When executed at e.g. 9am, displays: 25 Sep 2015
// When executed at e.g. 1pm, displays: 26 Sep 2015
So the reason your comparison then fails is because the date is actually completely different!
If you had tried simply using SameDateTime() for the comparison it may have appeared to have worked if you tested it in the morning but your problem would have returned in the afternoon !!
The Solution
You can work around these bugs in TCalendarEdit by ensuring that you respect the intended use of the property values yourself, assigning only those parts of the DT date/time value as appropriate in each case:
TimeEdit1.Time := TimeOf(DT);
CalendarEdit1.Date := DateOf(DT);
Although not strictly necessary in the case of the TTimeEdit, this will prevent these bugs in TCalendarEdit from causing these problems and makes it clear in your code that you are aware of what is required (consider it self documenting code if you like). :)
If you do not have TimeOf() and DateOf() functions in your version of Delphi, then the following is equivalent:
TimeEdit1.Time := DT - Trunc(DT);
CalendarEdit1.Date := Trunc(DT);
You could of course write your own versions of TimeOf() and DateOf() based on this, to make the intention clearer.
NOTE
There are precision complications arising from the floating point nature of date/time values in Delphi that could cause problems with direct comparisons with some specific values of date and time and for that reason it is highly recommended that you use the SameDateTime() function for performing such comparisons.
But this was absolutely not the cause of your problem in this case and SameDateTime() does not solve your problem.
SameDateTime() eliminates problems arising from differences in date/time values of less than 1 millisecond. The difference in this case was 24 hours!
Worth noting is that the TCalendarEdit control was deprecated in XE7 and has been removed entirely from XE8.
In continue to this question:
How to Convert Twitter Timestamp to DateTime?
what is the code to convert twitter date time stamp to TDateTime?
edit:
StrDateTime(const string;TFormatSettings);
could handle some of it,
now only to figure out how to intoduce new format.
Since we don't have the ParseExact function, you need to parse the components of the timestamp positionally. You could do it with the Copy() function. ex:
TheMonthAsString := Copy(TwitterDate,5,3);
TheDayAsString := Copy(TwitterDate,9,2);
etc..
Convert those pieces to Integers, and then you can use EncodeDateTime (in the DateUtils unit) (Thanks Jens!) to generate a TDateTime.
Summary: Pick the string apart into the individual components of the timestamp, and convert that to a TDateTime using EncodeDateTime or StrToDateTime.
I got the TDateTime thing fixed by using Floating vars to store them in a file. However, now I face a new problem: Invalid Floating Point - Most likely because of the Comma Separator.
How can I set the default separator in my program? Or is there any other way around?
You can use a TFormatSettings record to specify the decimal separator when you call StrToFloat and FloatToStr. You have to decide what to use and stick to that. Here is sample code with a .
var
d: TDateTime;
s: string;
fs: TFormatSettings;
begin
d := Now();
fs.DecimalSeparator := '.';
s := FloatToStr(d, fs);
end;
Another option would be to use the XML standard date time format. Delphi has some functions in XSBuiltIns to do the conversion from TDateTime to string and back. You will have a time zone offset in the value so if you move your persisted TDateTime from one time zone to another you may have some unwanted behavior. It depends on the usage of the value.
var
d: TDateTime;
s: string;
begin
d := Now();
s := DateTimeToXMLTime(d);
d := XMLTimeToDateTime(s);
end;
As Mikael suggested, there are a many ways to do this. To re-cap you wish to store a TDateTime to a file in textual format and be able to restore this value successfully irrespective of the locale on which the restoration happens.
Option 1
When storing, call FloatToStr, say, but force a '.' for the decimal separator through the TFormatSettings parameter. On restore, use StrToFloat with the same TFormatSettings.
Option 2
Encode the 8 byte TDateTime value using base 64. This has the downside that it renders the value unreadable.
Option 3
Similar to option 1, but encode the TDateTime by calling DateTimeToStr and explicitly passing a TFormatSettings that does not rely on anything in the locale – so do not rely on the locale's date or time separators, instead force your own. To reverse call StrToDateTime with an identical TFormatSettings record.