Delphi: How to determine and empty TDatetime value - delphi

Seems there is no way to assign NULL (either an "unassigned value" to TDateTime variables.
The only way I've imagined is using something like this:
function isNull(aDate : TDateTime) : boolean;
const NullDate = 0.0;
var aNullDate : TDatetime;
ms : Int64;
begin
aNullDate := NullDate;
ms := MilliSecondsBetween(aDate,aNullDate);
result := (ms = Int64(0));
end;
Is there anybody out who knows better solution what not overlaps 0 date value?
Are negative TDateTime values dangerous? (As an able resource for previous purpose)

As Andreas already wrote, the TDateTime type is actually double and thus not "nullable". I use
const
c_UnassignedDate = -693594;
for a empty date value as this represents an impossible date of 00/00/0000. But for example DevExpress uses
NullDate = -700000;
InvalidDate = NullDate + 1;
So there seems to be no agreed upon standard vale, you should pick one which suits your need.

First you need to define what you mean by 'an empty TDateTime value'.
A TDateTime value is a double with the date encoded in the integer part and the time encoded in the fractional part. So, the closest thing to a 'null date' you can get is probably 0.
Hence, simply test ADate <> 0 to test if the date is 'null'.
But beware: if you declare a TDateTime local variable then it will not necessarily be =0 before you give it a value. It can be anything. Of course, the same thing applies to variables of type integer, double, boolean, ...
Also, I believe that a TDateTime with value 0 encodes the date 1899-12-30.
Finally, negative TDateTime values are perfectly normal. For instance, -5000 corresponds to 1886-04-22.
I don't quite get the point of your code. If you want to use 0 as the 'unassigned' value (which is bad if you are interested in dates close to 1899-12-30), why not do simply
function IsUnassigned(ADate: TDateTime): boolean;
begin
result := ADate = 0;
end;
or, possibly (but not equivalently!),
function IsUnassigned(ADate: TDateTime): boolean;
begin
result := IsZero(Date);
end;
In his answer, ain gave a couple of more reasonable choices for the 'unassigned date' value.

At unit Spring.Persistence.Core.Session.pas of library Spring Framework for Delphi (http://www.spring4d.org) in method TSession.ExecuteScalar<T> in case of NULL for result used value Default(T);
I think, Your function can look like
function IsNull(ADate: TDateTime): Boolean;
begin
Result := ADate = Default(TDateTime);
end;

tDateTime is undefined for the values between 0 and -1 which means that a tDateTime of -0.5 is an undefined value and useful for a null date as an alternative to NaN. DateTimeToString will display -0.5 the same as +0.5;

Use PDateTime instead TDateTime, and send as nil value. If no pointer to value, result no value.
To check value use Assigned ptr :
MyDatePointer := nil;
if ( Assigned(MyDatePointer)) then ...

Related

What is the correct way to round currency data type in Delphi? [duplicate]

I'm trying to get Delphi to Round like Excel but I can't. Here is the code:
procedure TForm1.Button1Click(Sender: TObject);
var
s : string;
c : currency;
begin
c := 54321.245;
s := '';
s := s + Format('Variable: %m',[c]);
s := s + chr(13);
s := s + Format(' Literal: %m',[54321.245]);
ShowMessage(s);
end;
I'm using a currency variable that is set to 54321.245 and when I format this variable it rounds using Bankers Rounding. However, when I format the same value as a literal it rounds the way that Excel rounds.
I was expecting this to round to $54,321.25 whether it's formating a currency variable or a literal value. How can I make sure that Delphi rounds the same way as Excel every time?
Edit
The rounding I expect to see is as follows:
54,321.245 = 54,321.25
54,321.2449 = 54,321.24
54,431.2499 = 54,421.25
I am only using literals to show the different ways Delphi rounds. I expect to use variables in the actual code.
Note:
If I change the variable from currency to extended it rounds correctly
Edit #2
Some have suggested that I do not have a clear understanding of my requirements, this is absolutely not true. I have a very clear understanding of my requirements, I'm obviously not doing a very good job of explaining them. The rounding method I want is two decimal places. When the deimal part has a thousandths value >= 0.005 I want it rounded to 0.01 the currency type offered by Delphi does not do this. I also tried this example using Microsoft SQL with a money datatype (which I assumed was the same as Delphi's currency) and SQL rounds it's money type the way I described.
SQL Money >= 0.005 = 0.01
Delphi Currency >= 0.005 := 0.00
Edit #3
Good Article: http://rvelthuis.de/articles/articles-floats.html
Possible Solution: http://rvelthuis.de/programs/decimals.html
Edit #4
Here is one of the solutions from the Embarcadero discussion
function RoundCurrency(const Value: Currency): Currency;
var
V64: Int64 absolute Result;
Decimals: Integer;
begin
Result := Value;
Decimals := V64 mod 100;
Dec(V64, Decimals);
case Decimals of
-99 .. -50 : Dec(V64, 100);
50 .. 99 : Inc(V64, 100);
end;
end;
If I understand you correctly, you are looking for this:
function RoundTo2dp(Value: Currency): Currency;
begin
Result := Trunc(Value*100+IfThen(Value>0, 0.5, -0.5))/100;
end;
It's not possible to make RTL to round the way you want. The way to affect the rounding in Delphi is to use SetRoundMode which sets the FPU conrol word for rounding, however, as far as I can tell, there's no FPU support for rounding the exact in-between to upwards (which is generally avoided because it generates a bias for higher values).
You have to implement your own rounding function. There's an extended discussion in Delphi Rounding thread on Embarcadero forums, which includes several solutions.
use function System.Math.SimpleRoundTo
You can gain control on how delphi rounding numbers by :
uses Math;
...
procedure TForm1.Button1Click(Sender: TObject);
var
s : string;
c : currency;
begin
SetRoundMode(rmNearest);
c := 54321.245;
s := '';
s := s + Format('Variable: %m',[c]);
s := s + chr(13);
s := s + Format(' Literal: %m',[54321.245]);
ShowMessage(s);
end;
Unfortunately, using rmNearest, Delphi decides the number 54321.245 is closer to 54321.24 than 54321.25

How to get Delphi Currency Type to Round like Excel all the time?

I'm trying to get Delphi to Round like Excel but I can't. Here is the code:
procedure TForm1.Button1Click(Sender: TObject);
var
s : string;
c : currency;
begin
c := 54321.245;
s := '';
s := s + Format('Variable: %m',[c]);
s := s + chr(13);
s := s + Format(' Literal: %m',[54321.245]);
ShowMessage(s);
end;
I'm using a currency variable that is set to 54321.245 and when I format this variable it rounds using Bankers Rounding. However, when I format the same value as a literal it rounds the way that Excel rounds.
I was expecting this to round to $54,321.25 whether it's formating a currency variable or a literal value. How can I make sure that Delphi rounds the same way as Excel every time?
Edit
The rounding I expect to see is as follows:
54,321.245 = 54,321.25
54,321.2449 = 54,321.24
54,431.2499 = 54,421.25
I am only using literals to show the different ways Delphi rounds. I expect to use variables in the actual code.
Note:
If I change the variable from currency to extended it rounds correctly
Edit #2
Some have suggested that I do not have a clear understanding of my requirements, this is absolutely not true. I have a very clear understanding of my requirements, I'm obviously not doing a very good job of explaining them. The rounding method I want is two decimal places. When the deimal part has a thousandths value >= 0.005 I want it rounded to 0.01 the currency type offered by Delphi does not do this. I also tried this example using Microsoft SQL with a money datatype (which I assumed was the same as Delphi's currency) and SQL rounds it's money type the way I described.
SQL Money >= 0.005 = 0.01
Delphi Currency >= 0.005 := 0.00
Edit #3
Good Article: http://rvelthuis.de/articles/articles-floats.html
Possible Solution: http://rvelthuis.de/programs/decimals.html
Edit #4
Here is one of the solutions from the Embarcadero discussion
function RoundCurrency(const Value: Currency): Currency;
var
V64: Int64 absolute Result;
Decimals: Integer;
begin
Result := Value;
Decimals := V64 mod 100;
Dec(V64, Decimals);
case Decimals of
-99 .. -50 : Dec(V64, 100);
50 .. 99 : Inc(V64, 100);
end;
end;
If I understand you correctly, you are looking for this:
function RoundTo2dp(Value: Currency): Currency;
begin
Result := Trunc(Value*100+IfThen(Value>0, 0.5, -0.5))/100;
end;
It's not possible to make RTL to round the way you want. The way to affect the rounding in Delphi is to use SetRoundMode which sets the FPU conrol word for rounding, however, as far as I can tell, there's no FPU support for rounding the exact in-between to upwards (which is generally avoided because it generates a bias for higher values).
You have to implement your own rounding function. There's an extended discussion in Delphi Rounding thread on Embarcadero forums, which includes several solutions.
use function System.Math.SimpleRoundTo
You can gain control on how delphi rounding numbers by :
uses Math;
...
procedure TForm1.Button1Click(Sender: TObject);
var
s : string;
c : currency;
begin
SetRoundMode(rmNearest);
c := 54321.245;
s := '';
s := s + Format('Variable: %m',[c]);
s := s + chr(13);
s := s + Format(' Literal: %m',[54321.245]);
ShowMessage(s);
end;
Unfortunately, using rmNearest, Delphi decides the number 54321.245 is closer to 54321.24 than 54321.25

Delphi TNumberbox returns single; how to get to a standard integer?

TNumberbox and TSpinEdit return values defined as type single. I want to use these values to do simple integer arithmetic, but I can't cast them successfully to the more generalized integer type, and Delphi gives me compile-time errors if I try to just use them as integers. This code, for example, fails with
"E2010 Incompatible types: 'Int64' and 'Extended'":
var
sMinutes: single;
T: TDatetime;
begin
sMinutes :=Numberbox1.value;
T :=incminute(Now,sMinutes);
All I want to do here is have the user give me a number of minutes and then increment a datetime value accordingly. Nothing I've tried enables me to use that single in this way.
What am I missing??
Just truncate the value before using it:
var
Minutes: Integer;
T: TDateTime;
begin
Minutes := Trunc(NumberBox1.Value);
T := IncMinute(Now, Minutes);
end;
Depending on your particular needs, you may need to use Round instead. It will correctly round to the nearest integer value, making sure that 1.999999999999 correctly becomes integer 2; Trunc would result in 1 instead. (Thanks to Heartware for this reminder.)
var
Minutes: Integer;
T: TDateTime;
begin
Minutes := Round(NumberBox1.Value);
T := IncMinute(Now, Minutes);
end;
Trunc and Round are in in the System unit.

Avoiding Locale Conflicts when storing TDateTime's in Floats?

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.

How to use TComparer to sort a list of records?

I have a record that holds data about a file:
TYPE
RFile= record
public
FileName : string;
Resolution : Integer;
FileSize : Cardinal;
Rating : Byte;
end;
PFile= ^RFile;
And I keep a list of these files/records in a TList<>
TFileList= class(TList<PFile>)
procedure SortByFilename;
procedure SortByRating;
procedure SortByResolution;
procedure SortBySize;
end;
I have methods like SortByFilename, SortBySize, etc in which I sort the list.
I do "classic" sorting.
Now I want to upgrade to the new-and-cool System.Generics.Defaults.TComparer.
From what I understand I need to assign a comparer to my TFileList, like
TIntStringComparer = class(TComparer<String>)
public
function Compare(const Left, Right: String): Integer; override;
end;
How do I do this?
How do I deal with one comparer for each data field (filename, filesize, resolution)?
Update:
This code compiles but I have an EIntegerOverflow because the FileSize is a cardinal while I return an integer (diff between two cardinals).
Sort(TComparer<PFile>.Construct(
function(CONST A,B: PFile): integer
begin
Result:= A.FileSize - B.FileSize;
end
));
When you write a comparer for a numeric type, you should never use subtraction, even if your data type is signed.
Indeed, try to compare a = 100 and b = -2147483640 as Integers; clearly a > b, but subtraction will yield the wrong result.
Instead, you should always do something similar to
if a = b then
Result := 0
else if a < b then
Result := -1
else
Result := 1;
But Delphi's RTL already contains functions for this: there are several CompareValue overloads in the Math unit (for different types of integers and floats -- but, unfortunately, not for Cardinals).
Thus, although your snippet will work "most of the time" if you do
Result := Integer(A.FileSize) - Integer(B.FileSize)
this is not good enough: For one thing, not every Cardinal will fit in a Integer. Also, as noted above, subtraction is not the way to go.
In your case, you can simply use the if thing above directly, or you could create a new CompareValue overload for Cardinals. Or, you could do
Result := CompareValue(Int64(A.FileSize), Int64(B.FileSize)).
(Also, as others have stated in comments, you should reconsider if it is wise to use a Cardinal to store a file size in the first place. If you upgrade this to an Int64 or UInt64 you can write simply
Result := CompareValue(A.FileSize, B.FileSize).)

Resources