“0.0” 访问 TClientDataSet 的 TDateField 的 OldValue 时不是有效的时间戳

发布于 2024-07-19 03:39:39 字数 505 浏览 2 评论 0原文

当我在 TClientDataSet 中新插入的记录中使用以下代码时:

cdsMyDateField.OldValue <> Null

我得到一个 EConvertError:

''0.0' is not a valid timestamp'.

查看 Delphi 的 VCL 代码,它尝试将值转换为 TDateTime,这会导致此异常,因为值(Null)是无效的 DateTime,但当我比较变体时,我认为它会返回一个变体,在这种情况下该变体将为 Null,但这并没有发生,而是我得到了这个异常。

我知道我可以在比较值之前检查 DataSet.State = dsInsert ,就好像 State = dsInsert 每个 OldValue 都是 Null 一样,但我想了解为什么 OldValue 尝试转换值而不是全部返回 Null State = dsInsert 时的字段。

谁能给我一些启发吗?

When I use the following code in a newly inserted record in a TClientDataSet:

cdsMyDateField.OldValue <> Null

I get an EConvertError:

''0.0' is not a valid timestamp'.

Looking at Delphi's VCL's code, it tries to convert the value to TDateTime which results in this Exception because the value (Null) is an invalid DateTime, but as I'm comparing Variants I thought that it would return a variant, which would be Null in this case, but that doesn't happen, instead I get this exception.

I know that I can just check if the DataSet.State = dsInsert before comparing the values, as if the State = dsInsert every OldValue is Null, but I want to understand why the OldValue tries to convert the value instead of Just returning Null in all Fields when the State = dsInsert.

Can anyone please give me some light?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

烂柯人 2024-07-26 03:39:40

在 Delphi 2009 update 2 和 Delphi 2007 中重现它,如下所示:

uses DB, DBClient;
procedure TTestForm1.TestButtonClick(Sender: TObject);
const
  SMyDateField = 'MyDateField';
  SMyIntegerField = 'MyIntegerField';
var
  MyClientDataSet: TClientDataSet;
  MyClientDataSetMyDateField: TField;
  MyClientDataSetMyIntegerField: TField;
  OldValue: Variant;
begin
  MyClientDataSet := TClientDataSet.Create(Self);
  MyClientDataSet.FieldDefs.Add(SMyDateField, ftDate);
  MyClientDataSet.FieldDefs.Add(SMyIntegerField, ftInteger);
  MyClientDataSet.CreateDataSet();
  MyClientDataSetMyDateField := MyClientDataSet.FieldByName(SMyDateField);
  MyClientDataSetMyIntegerField := MyClientDataSet.FieldByName(SMyIntegerField);
  MyClientDataSet.Insert();
  OldValue := MyClientDataSetMyIntegerField.OldValue;
  OldValue := MyClientDataSetMyDateField.OldValue;
end;

您总是收到此错误:

exception class EConvertError with message ''0.0' is not a valid timestamp'.

我不确定这是否被视为错误:

  • 插入时技术上没有 OldValue,因此获取它可以引发异常
  • MyClientDataSetMyIntegerField.OldValue 返回 0,但 MyClientDataSetMyDateField.OldValue 引发异常

更多说明:

  • TCustomClientDataSet.GetFieldData 将获取实际的底层数据
  • TDataSet.DataConvert 将底层数据转换为原生数据格式,执行有效性检查在需要的地方

编辑:根据 Fabricio 的评论,我强调 OldValue 在插入后在技术上是无效的。 所以从技术上来说这可能不是一个错误。

他的“新证据”可以通过检查 VCL/RTL 源来验证:

对于字段类型 ftDate、ftTime、ftDateTime,TDataSet.DataConvert 调用其本地 NativeToDateTime 函数,该函数填充 TimeStamp,然后使用 SysUtils.TimeStampToDateTime 转换该函数,而 SysUtils.TimeStampToDateTime 又调用 SysUtils。 ValidateTimeStamp,当时间部分小于零或日期部分小于或等于零时引发异常。

仅对于字段类型 ftDate 和 ftDateTime(TDateField 和 TDateTimeField),日期部分可以变为零,因此只有那些字段类型可以引发异常。
对于所有其他数据类型,NativeToDateTime 不会有问题:这些类型都允许结果填充零字节。

我刚刚检查了 RTL/VCL 历史记录:自从 Delphi 6 SysUtils.TimeStampToDateTime 调用 SysUtils.ValidateTimeStamp 以来,这种行为自 2001 年以来一直是相同的。

这使得很难将其视为错误。

Got it reproduced in Delphi 2009 update 2 and Delphi 2007 as follows:

uses DB, DBClient;
procedure TTestForm1.TestButtonClick(Sender: TObject);
const
  SMyDateField = 'MyDateField';
  SMyIntegerField = 'MyIntegerField';
var
  MyClientDataSet: TClientDataSet;
  MyClientDataSetMyDateField: TField;
  MyClientDataSetMyIntegerField: TField;
  OldValue: Variant;
begin
  MyClientDataSet := TClientDataSet.Create(Self);
  MyClientDataSet.FieldDefs.Add(SMyDateField, ftDate);
  MyClientDataSet.FieldDefs.Add(SMyIntegerField, ftInteger);
  MyClientDataSet.CreateDataSet();
  MyClientDataSetMyDateField := MyClientDataSet.FieldByName(SMyDateField);
  MyClientDataSetMyIntegerField := MyClientDataSet.FieldByName(SMyIntegerField);
  MyClientDataSet.Insert();
  OldValue := MyClientDataSetMyIntegerField.OldValue;
  OldValue := MyClientDataSetMyDateField.OldValue;
end;

You always get this error:

exception class EConvertError with message ''0.0' is not a valid timestamp'.

I'm not sure if this is to be regarded as a bug:

  • upon insert there is technically no OldValue, so obtaining it can raise an exception
  • MyClientDataSetMyIntegerField.OldValue returns 0, but MyClientDataSetMyDateField.OldValue raises an exception

Some more notes:

  • TCustomClientDataSet.GetFieldData will get the actual underlying data
  • TDataSet.DataConvert will convert the underlying data to the native data format, performing validity checks where needed

Edit: as a result of Fabricio's comment I have stressed that OldValue is technically invalid after an insert. So technically this might not be a bug.

His 'new evidence' can be verified by checking the VCL/RTL sources:

For fieldtypes ftDate, ftTime, ftDateTime, TDataSet.DataConvert calls its local NativeToDateTime function which fills a TimeStamp, then converts that using SysUtils.TimeStampToDateTime which in turn calls SysUtils.ValidateTimeStamp which raise an exception when the Time portion is less than zero, or the Date portion is less or equal to zero.

The Date portion can become zero only for fieldtypes ftDate and ftDateTime (TDateField and TDateTimeField), hence only those can raise an exception.
For all other data types, NativeToDateTime will not have problems: those types all allow for a result filled with zero-bytes.

I just checked the RTL/VCL history: since Delphi 6 SysUtils.TimeStampToDateTime calls SysUtils.ValidateTimeStamp, so this behaviour has been the same since 2001.

That makes it really hard to regard this as a bug.

九命猫 2024-07-26 03:39:40

我将 delphi 2007 与 MySQL 一起使用,我的解决方案是:

在 MySQL 查询中,我使用了:

select CAST(dateField AS CHAR) from table_name

... 使用 CAST 将字段转换为字符串值,我可以测试该值是否为 0000-00-00 ...然后使用字段的实际值。

I'm using delphi 2007 with MySQL and my solution is:

In MySQL query I've used:

select CAST(dateField AS CHAR) from table_name

... using CAST I converted the field to a string value and I can test if the value is 0000-00-00 ... and after this use the real value of field.

南…巷孤猫 2024-07-26 03:39:39

FWIW,我遇到了同样的问题,这让我很头疼。 我的观点:行为不一致,因此即使仅从这个原因我也将其归类为错误。 这也是一个错误,因为在读取属性时引发异常是荒谬的,并且不符合属性的意图(最小惊喜原则)。我希望 OldValue 未分配,而不是在读取时引发异常。 (此外,某些问题已经存在很长时间的事实并不意味着它是否是一个错误。)

(编辑:用更多信息更新我的答案,包括我们的解决方法。也将同样的内容发布到质量控制报告中: http://qc.embarcadero.com/wc/qcmain.aspx?d =73852 )

我们在大量使用 datasnap/clientdatasets 的应用程序中也遇到了同样的问题。 然而,实际的问题不在于客户端数据集,而在于 SysUtils 中的时间戳验证例程,该例程显然错误地将值为 0.0 的时间戳验证为无效。

因此,解决方法/修复是针对 SysUtils 和验证例程,而不是针对 Tclientdataset。具体来说,在验证例程“ValidateTimeStamp()”中,时间部分被正确地比较<<。 0 但日期部分被错误地比较 <= 0。

因此,(合法的)0.0 日期时间值有时会转换为日期部分 = 0 的时间戳,并且当该值再次进行反向验证时(例如,当从数据集字段(如此处和 QC 报告中所示),会(错误地)引发异常。 因此,将时间戳的日期部分的验证更改为使用严格小于的简单修复可以解决 TClientDataset 暴露的问题。

这是我们基于 Delphi 7.1 的解决方法

(* SysUtils.pas line 10934 (Delphi 7.1)  *)
(**)(* fix - Timestamp values 0.0 erroneously designated as invalid *)
(* D7.1 *)
(* Walter Prins, originally patched May 2005, submitted 4 June 2009 *)
procedure ValidateTimeStamp(const TimeStamp: TTimeStamp);
begin
  if (TimeStamp.Time < 0) or (TimeStamp.Date < 0) then (* Changed TimeStamp.Date <= 0 to TimeStamp.Date < 0 *)
    ConvertErrorFmt(@SInvalidTimeStamp, [TimeStamp.Date, TimeStamp.Time]);
end;

FWIW, I've run into this same problem and it caused me several headaches. My opinion: The behaviour is inconsistent so even from that reason alone I class it as a bug. It's also a bug because raising an exception on reading a property is IMHO ridiculous and not in keeping with the intent of properties (principle of least surprise.) I'd expect OldValue to be Unassigned, not raise an exception on read. (Also, the fact that some issue has existed for a long time does not imply anything about whether or not it's a bug.)

(Edit: Updating my answer with more information including our workaround. Also posted same to the QC report: http://qc.embarcadero.com/wc/qcmain.aspx?d=73852 )

We've also had this same problem in an app that makes heavy use of datasnap/clientdatasets. The actual problem however is not with the clientdataset, but with the Timestamp validation routine in SysUtils that apparently erroneously validates a Timestamp with a 0.0 value as invalid.

The workaround/fix is hence to SysUtils and the validation routine, and not to Tclientdataset Specifically, in the validation routine "ValidateTimeStamp()", the Time portion is correctly compared < 0 but the Date portion is erroneously compared <= 0.

Consequently a (legitimate) 0.0 Datetime value will sometimes be converted to a Timestamp with datepart = 0, and when this value is again reverse validated (such as when the value is read from a dataset field as shown here and in the QC report), an exception is (erroneously) raised. Hence the simple fix of changing the validation for the Date portion of the timestamp to use strict less-than, fixes the problem exposed by TClientDataset.

Here's our workaround, based on Delphi 7.1

(* SysUtils.pas line 10934 (Delphi 7.1)  *)
(**)(* fix - Timestamp values 0.0 erroneously designated as invalid *)
(* D7.1 *)
(* Walter Prins, originally patched May 2005, submitted 4 June 2009 *)
procedure ValidateTimeStamp(const TimeStamp: TTimeStamp);
begin
  if (TimeStamp.Time < 0) or (TimeStamp.Date < 0) then (* Changed TimeStamp.Date <= 0 to TimeStamp.Date < 0 *)
    ConvertErrorFmt(@SInvalidTimeStamp, [TimeStamp.Date, TimeStamp.Time]);
end;
森罗 2024-07-26 03:39:39

Delphi 中的 TDateTime 是双精度型,其中日期存储在整数部分,时间存储在小数部分。 因此,在某种程度上,将空日期值转换为 0.0 是正确的。 由于您正在访问的基础字段是 TDateField (或 TDateTime 字段),因此它可能在内部进行转换。

此外,在 Delphi 中检查 Variant 是否为 Null 不再是正确的方法。 Null 变体仍会被分配,但值为 Null,而未分配的变体则没有值。 (想想 SQL 数据库 NULL 值)。 请改用 Variants.pas 单元中的 VarIsNull(const V: Variant) 函数; 如果变量为 null,则返回 true;如果有任何其他值,则返回 false。

A TDateTime in Delphi is a double, where the date is stored in the whole number portion and the time is stored in the fractional portion. So a conversion of an empty date value to 0.0 is correct, to some extent. Since the underlying field you're accessing is a TDateField (or TDateTime field), it's probably doing the conversion internally.

In addition, checking a Variant against Null isn't the proper way in Delphi any more. A Null variant is still assigned, but has the value Null, whereas an unassigned variant has no value. (Think of an SQL database NULL value). Use the VarIsNull(const V: Variant) function found in the Variants.pas unit instead; it returns true if the variant is null, false if it has any other value.

往事随风而去 2024-07-26 03:39:39

我在激活“调试 DCU”选项的情况下调试了下面的代码
奇怪的是 SysUtils.ValidateTimeStamp 评估
日期 = 0 的时间戳无效,因此抛出
EConvertError 异常(而不是返回 Null 或未分配)。

所以最终结果是对 null 字段执行 OldValue 请求
dsInsert 状态是其无效。 该变体从不返回
因此,如果您使用 (field.OldValue <> Null) 或
VarIsNull(field.OldValue)。 之前抛出了异常。

cds_something 有两个字段(在设计时创建):

  • dt_Something
  • num_something

代码:

var
  b: TClientDataset;
begin
  b := cds_somethin;
  b.Close;
  b.CreateDataSet;
  b.Insert;
  if b.FieldByName('DT_Sometinhg').OldValue <> Null then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');

  b.Cancel;

注意:我搞乱了这篇文章的原始编辑。 现在这是
对我所发现的内容的正确解释。

添加:使用其他一些字段类型(字符串、BCD、Float 和 Memo)和 OldValue 进行测试
未分配 - 因此上面的测试将评估为 false。

似乎只有 TDateField 和 TDateTimeField 显示该行为。 TTimeField 和 TSQLTimeStamp 评估正常 - 但 TSQLTimeStampField.OldValue 都不等于
为 Null 或未分配(wtf!!)...

代码片段稍作更改:

var
  b: TClientDataset;
begin
  b := cds_somethin;
  b.Close;
  b.CreateDataSet;
  b.Insert;
  /*
  if (b.FieldByName('DT_Something').OldValue <> Null) 
     and (b.FieldByName('DT_Something').OldValue <> Unassigned)  then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');
  */
  if (b.FieldByName('ts_Something').OldValue <> Null) 
     and (b.FieldByName('ts_Something').OldValue <> Unassigned)  then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');


  b.Cancel;

其中 ts_Something 是 TSQLTimeStampField。 这些字段是在设计时创建的。

I debugged the code below with Debug DCUs option activated
and the weird thing is that SysUtils.ValidateTimeStamp evaluates
a TimeStamp with date = 0 to be invalid and therefore throwing a
EConvertError exception (instead of return Null or Unassigned).

So the end result is that doing an OldValue request to a null field in
a dsInsert state is that its invalid. The variant is NEVER returned
so it is irrelevant if you test it with (field.OldValue <> Null) or
VarIsNull(field.OldValue). The exception gets thrown before.

The cds_something has two fields (created at design time):

  • dt_Something
  • num_something

Code:

var
  b: TClientDataset;
begin
  b := cds_somethin;
  b.Close;
  b.CreateDataSet;
  b.Insert;
  if b.FieldByName('DT_Sometinhg').OldValue <> Null then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');

  b.Cancel;

Note: I messed up with the original editon of this post. This now is the
correct interpretation of what I find.

Addition: tested with some other field types (string, BCD, Float and Memo) and OldValue
is Unassigned - so the test above will evaluate to false.

Appear that ONLY TDateField and TDateTimeField show that behavior. TTimeField and TSQLTimeStamp evaluates normal - but TSQLTimeStampField.OldValue does not equal neither
to Null or Unassigned (wtf!!)...

The snippet changed a little:

var
  b: TClientDataset;
begin
  b := cds_somethin;
  b.Close;
  b.CreateDataSet;
  b.Insert;
  /*
  if (b.FieldByName('DT_Something').OldValue <> Null) 
     and (b.FieldByName('DT_Something').OldValue <> Unassigned)  then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');
  */
  if (b.FieldByName('ts_Something').OldValue <> Null) 
     and (b.FieldByName('ts_Something').OldValue <> Unassigned)  then
    ShowMessage('Something wrong!!!')
  else
    ShowMessage('Normal');


  b.Cancel;

Where ts_Something is a TSQLTimeStampField. The fields are created at design time.

云之铃。 2024-07-26 03:39:39

这是 Midas/TClientDataSet 上与记录缓冲区相关的错误。 当您多次执行 AppendData 时,InternalCalc 字段显示为“not null”(记录缓冲区中错误的 null 标志)。 在Delphi 2010中,我们可以研究Midas.dll源代码(.cpp文件)。

http://img514.imageshack.us/img514/2840/wrongnull.jpg

我正在研究如何实施解决方案。

艾尔·冈萨雷斯.

This is a bug on Midas/TClientDataSet related to record buffers. When you do AppendData many times, the InternalCalc fields appears as "not null" (wrong null flag in record buffer). In Delphi 2010 we can study Midas.dll sources (.cpp files).

http://img514.imageshack.us/img514/2840/wrongnull.jpg

I am researching how to implement a solution.

Al Gonzalez.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文