1

I have a similar case as in this question.

procedure TForm2.FormCreate(Sender: TObject);
var
  S: string;
  C: Currency;
  FormatSettings: TFormatSettings;
begin
  S := '1.000.000,00';
  FormatSettings := TFormatSettings.Create;
  FormatSettings.ThousandSeparator := '.';
  FormatSettings.DecimalSeparator := ',';
  // raises an Exception which is "as designed" as per the documentation
  C := StrToCurr(S, FormatSettings);
  ShowMessage(FormatCurr(',0.00', C));
end;

To quote David:

So it is a mistake to pass a string containing a thousands separator to this function.

So then does Delphi have any built-in function that will parse currency strings that contain thousand separators?

Jens Mühlenhoff
  • 14,565
  • 6
  • 56
  • 113
  • Strip the thousands seps – David Heffernan Aug 07 '16 at 19:01
  • ThousandSeparator & DecimalSeparator using only for DISPLAY currency in proper format – Zam Aug 07 '16 at 19:19
  • @Zam, only `ThousandSeparator` is purely decorative, `DecimalSeparator` is significant. Here is my $2. – Free Consulting Aug 07 '16 at 20:19
  • @FreeConsulting, yes, according to ASM code you are 100% right. `MOV AX,[EAX].TFormatSettings.DecimalSeparator` from System.SysUtils, function `TextToFloat` – Zam Aug 07 '16 at 20:22
  • When bigger numbers are involved users like to have the ThousandSeparator (some might actually prefer scientific notation), so I think that there is a `GlorifiedStrToFloat` missing for round-trips through the user interface that can handle these cases. – Jens Mühlenhoff Aug 08 '16 at 10:31

2 Answers2

6

The solution to this design (flaw) is simple: define your own function.

unit MyFixForSysUtils;

interface

function StrToCurr(const Str: string): Currency; overload;
function StrToCurr(Str: string; const FormatSettings : TFormatSettings): Currency; overload;  

implementation

uses SysUtils;

function StrToCurr(Str: string; const FormatSettings : TFormatSettings): Currency;
begin
  Str:= StringReplace(Str, FormatSettings.ThousandSeparator, '', [rfReplaceAll]);
  Result:= SysUtils.StrToCurr(Str, FormatSettings);
end;

 function StrToCurr(const Str: string): Currency;
 begin
   Result:= StrToCurr(Str, FormatSettings);
 end;

If you make sure your own version is closer in scope than sysutils, then you don't need to change your code:

uses
  SysUtils,
  MyFixForSysUtils,  <-- contains the above function
  .... other units.

Now Delphi will select the fixed function instead of the broken one.

For more info on this concept see: Delphi interposer

Johan
  • 74,508
  • 24
  • 191
  • 319
  • 1
    Not a design flaw. Perfectly reasonable design and one found in so many other libraries. – David Heffernan Aug 08 '16 at 07:05
  • I chose to give the function a new name, because I think that it's easier to refactor the function calls rather than to rely on the uses order staying the same. I also like to avoid global namespace pollution so I made it a static class method of a utility class. – Jens Mühlenhoff Aug 08 '16 at 10:24
  • Most parts of the program use database components (like `TDBEdit`) anyway and there it's just a matter of assigning `DisplayFormat := ',0.00';`. Sometimes you really do want the original function, with a fixed FormatSettings (i.e. text file interfaces). – Jens Mühlenhoff Aug 08 '16 at 10:26
4

You can use VarCyFromStr from 'varutils.pas' which by default points to the COM helper VarCyFromStr imported from 'oleaut32' in 'activex.pas' (which you can directly use).

If you know the string is localized with the system default locale, you can use:

var
  S: string;
  C: Currency;
begin
  S := '1.000.000,00';
  if varutils.VarCyFromStr(S, 0, LOCALE_NOUSEROVERRIDE, C) = VAR_OK then
    ShowMessage(FormatCurr(',0.00', C))
  else

or pass GetThreadLocale for LCID to use the current thread's settings.

If the string is localized with the default user locale you can make the RTL call it for you which uses this function for variant conversion.

var
  V: Variant;
  C: Currency;
begin
  V := '1.000.000,00';
  C := V;
  ShowMessage(FormatCurr(',0.00', C));

Otherwise you have to know in which locale the string represents a currency. Example:

var
  S: string;
  C: Currency;
begin
  S := '1,000,000.00';
  if varutils.VarCyFromStr(S, MAKELCID(LANG_ENGLISH, SORT_DEFAULT), 0, C) = VAR_OK then
    ShowMessage(FormatCurr(',0.00', C))
  else
    // handle error
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169