The essence in the following:
procedure TForm1.Button1Click(Sender: TObject);
var
cfile: TInifile;
Date1: TDateTime;
begin
Date1 := IncYear(Now, -50);
cfile := TInifile.Create(ExtractFilePath(Application.ExeName) + 'Settings.ini');
try
cfile.WriteDateTime('Main', 'DateTime', Date1);
ShowMessage('Recorded in the ini file ' + DateTimeToStr(Date1));
Date1 := cfile.ReadDateTime('Main', 'DateTime', Now);
ShowMessage('Read from ini file ' + DateTimeToStr(Date1));
finally
cfile.Free;
end;
end;
Entry in the ini file passes without problems. In the file is written to 04-Dec-63 17:28:14. Read also from ini file does not work, the message falls "04-Dec-63 17:28:14 is not a valid date and time".
Windows 7 Enterprise х32, Embarcadero Delphi XE Portable
You've written the date/time to the file as text. And formatted it using the locale settings of the user who created that file. You are doomed to fail to read this file reliably since different users have different locale settings. You need to use a robust format for the date that does not depend on locale.
The two options that seem most natural:
Store as a floating point value, using the underlying representation of TDateTime.
Store as text using a pre-determined format.
For option 1 you'll need to make sure you use a pre-determined decimal separator to avoid the exact same problem you have now! That means you'll need to perform your own conversion between TDateTime and string because the WriteFloat and ReadFloat methods use the global format settings which are locale dependent. There are overloads of FloatToStr and StrToFloat in SysUtils that accept a format settings parameter.
For option 2, the RTL contains various functions to perform date/time conversions using specified formats. There are overloads of DateTimeToStr and StrToDateTime in SysUtils that accept a format settings parameter.
Option 2 is to be preferred if you wish the file to be easily read or edited by a human.
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;
Who can to advice in my problem.
I set date format as 'JUL/12 - 12 15:35', but when using StrToDateTime then give EConvertError.
What can I do with this format which contains 2 - date separator ?
Use next code
function LocaleFormatStrToDateTime(const S: string): TDateTime;
var
LFormatSettings: TFormatSettings;
begin
LFormatSettings := GetLocaleFormatSettings(LOCALE_SYSTEM_DEFAULT);
LFormatSettings.ShortTimeFormat := FormatSettings.ShortTimeFormat;
LFormatSettings.TimeSeparator := FormatSettings.TimeSeparator;
Result := StrToDateTime(S, LFormatSettings);
end;
----------
**
the best solution is use jvDateUtil.StrToDate*
**
Your format is completely non-standard (and almost incomprehensible), so can't be handled by the built-in Date/Time formatters.
You've designed your own format, so you need to write your own code to convert to and from it.
This is nature's way of telling you not to use wacky date and time formats!
Probably the string you're trying to convert is not compatible with the default system format. Taking a look at the method signature and description reveals that you can override it to suit your needs, see an example here.
It would be helpful if you posted a piece of the code you have so far, maybe you overlooked something.
EDIT
I've missed the fact that your're using a complex format, including multiple separators for the date, which I'm not sure that are supported in delphi.
I guess that in this case you could split your string into pieces and then encode them into a TDateTime. To convert your month name to a month number you can iterate through the LFormatSettings.ShortMonthNames array, something like:
String longMonth:= copy(S, 0, 3);
for i := Low(LFormatSettings.ShortMonthNames) to High(LFormatSettings.ShortMonthNames) do
if SameText(longMonth, LFormatSettings.ShortMonthNames[i]) then begin
shortMonth:=FormatFloat('00', i);
Break;
end;
Currently, I'm setting DecimalSeparator to a '.' in each procedure which uses these functions.
It would be much easier to set this globally at the start of the program but I found Delphi seems to periodically set this back to the current locale.
I need to make sure that a decimal point is used for all conversions no matter which country the program is used in as this is the standard for this type of program and all files structure and communication protocols, numeric displays in forms/edits etc are required to be formatted in this way.
I've been told in another thread that using decimalseparator is not the correct way to do it but I was not given any alternatives. The other threads concerning this subject that I've read don't seem to offer any formative guidance or are overly complex.
Is there a simple 'correct' way to do this ?
Yes, the DecimalSeparator global variable might be changed by the RTL during runtime, which caused a lot of headache for me a few years ago before I realised this.
The thing is that DecimalSeparator is updated by the RTL when the Windows decimal separator is changed, for instance, using the Control Panel. This might seem like a rather small problem. Indeed, how often does the end user change the system's decimal separator?
The big issue is that the DecimalSeparator variable is updated (according to the system setting) every time you switch user (in Windows). That came as a surprise to me. That is, if your system setting uses a comma (',') as the decimal separator, and you set DecimalSeparator := '.' at application startup, then DecimalSeparator will revert to a comma if you switch user (and you'll notice that when you switch back).
You can tell the RTL not to update the decimal separator by
Application.UpdateFormatSettings := false;
At any rate, there are better alternatives to DecimalSeparator, as discussed in other answers and comments.
I am/was under the assumption that the global DecimalSeperator variable would not be touched by the RTL. If not, then all these routines have an optional parameter FormatSettings which you could use. Globaly declare a TFormatSettings variable and use it for each occurance of these routines.
A small benefit of it could be that the routines are thread-safe when you specify your own format settings.
To be on the safe side, i would use TFormatSettings, this has two advantages:
The formatting is thread safe, other code/libraries cannot influence your function.
You do not influence other code, which possibly rely upon certain settings.
Here a possible implementation:
function FloatToStrWithDecimalPoint(const Value: Extended; const Format: String = '0.###'): String;
var
myFormatSettings: TFormatSettings;
begin
GetLocaleFormatSettings(GetThreadLocale, myFormatSettings);
myFormatSettings.DecimalSeparator := '.';
Result := FormatFloat(Format, Value, myFormatSettings);
end;
You could patch every string before and after calling a RTL function with some ForceLocalSeparator() and ForceDotSeparator() functions.
// before a RTL call
Function ForceLocalSeparator(Const StrValue: String): String;
Var
SepPos: Integer;
Begin
Result := StrValue;
If DecimalSeparator <> '.' Then
Begin
SepPos := Pos( '.', Result );
If SepPos > 0 Then Result[SepPos] := DecimalSeparator;
End;
End;
// after a RTL call
Function ForceDotSeparator(Const StrValue: String): String;
Var
SepPos: Integer;
Begin
Result := StrValue;
If DecimalSeparator <> '.' Then
Begin
SepPos := Pos( DecimalSeparator, Result );
If SepPos > 0 Then Result[SepPos] := '.';
End;
End;
It's OK if you have no alternative. Prefer the versions of those functions that accept a TFormatSettings parameter, if your Delphi version is recent enough, so that you don't interfere with any other code that relies on that global variable for locale-aware behavior.
FloatToStr and StrToFloat are locale-sensitive functions. If you need to convert your floating-point value to a string to persist it somewhere that a program will read later (such as to a file, the registry, or a network socket), then you should use the locale-independent functions Str and Val for your conversions instead. They always use . for the decimal separator, regardless of the DecimalSeparator variable or other environmental settings.
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.
I use Delphi 2006 and ADO to connect to a MS Access database. Some of the fields I retrieve are Date fields (in Access formatted as "Medium Date" i.e. 20-Apr-2010) however I have to retrieve them as Strings:
FValue:=FAccessADOQuery.Fields.FieldByName(FIELD_NAME).AsString;
and then the fields are formatted as follows: 4/20/2010.
My question is: when does this formatting take place and how can I customize it? Is it ADO settings (could not find anything there) or the OS (I use Win XP ENG with US locale)? Or maybe it's Delphi?
Thanks!
Lou
the ShortDateFormat and LongTimeFormat variables are used to format an TDateTimeField to string.
you can change the value of theses variables or try something different like this :
Dt :TDateTime;
Ds :String;
begin
//FAccessADOQuery.Fields.FieldByName(FIELD_NAME).AsString
Dt:=FAccessADOQuery.Fields.FieldByName(FIELD_NAME).AsDateTime;
Ds:=FormatDateTime('dd-mmm-yyyy',dt);
end;
Ok, just found it. It's delphi general settings (if missing then the values are taken from the OS):
DateSeparator := '-';
ShortDateFormat := 'dd-mmm-yyyy';
And now the returned value is "20-Apr-2010".
You can retreive the Value as DateTime and use this function to convert it to your format
FValue:=FAccessADOQuery.Fields.FieldByName(FIELD_NAME).AsDateTime;
function DateToMediumDate(const Date: TDate): string;
var
y, m, d: Word;
begin
DecodeDate(Date, y, m , d);
Result := Format('%d-%s-%d', [d, ShortMonthNames[m], y]);
end;