首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何避免TDateTime数据舍入

如何避免TDateTime数据舍入
EN

Stack Overflow用户
提问于 2015-10-06 17:23:41
回答 2查看 776关注 0票数 2

我正在为FMX TGrid编写列和单元类,这些类将在每个单元格中包含TCalendarEditTTimeEdit实例。除了正确处理这些子控件中所做的更改外,一切都正常工作。

代码语言:javascript
复制
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;

其思想是通过TGrid OnGetValue事件处理程序分配数据。同时显示日期和时间。捕获用户活动并设置Modified标志。问题是,即使没有任何用户活动,也会将此标志设置为true。我怀疑这是由于TDateTime时间的四舍五入造成的。代码没有其他方法将值分配给FCalendarEdit.DateFTimeEdit.Time

如何正确地比较存储在FCalendarEdit.DateFTimeEdit.Time中的数据和存储在FDate_Time.FieldValue中的数据

附录

以这种方式设置标志并不能解决问题。

代码语言:javascript
复制
  FDate_Time.Modified := (FDate_Time.Modified) or
    (DateOf(FDate_Time.FieldValue) <> FCalendarEdit.Date) or
    (TimeOf(FDate_Time.FieldValue)<> FTimeEdit.Time);

附加了2.关于@Ken的宝贵建议。如果我们把比较线替换成

代码语言:javascript
复制
FDate_Time.Modified := (FDate_Time.Modified) or
(not SameDateTime(FDate_Time.FieldValue,
 FCalendarEdit.Date + FTimeEdit.Time));

效果很好。因此,TDataTime比较必须仅由该函数进行。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-10-06 19:26:33

TDateTimetype Double,这意味着它是一个浮点值,因此在不指定可接受的增量(差异)的情况下进行等式比较时会遇到二进制表示的常见问题。

特别是对于TDateTime值,您可以使用DateUtils.SameDateTime将等式降低到小于1毫秒:

代码语言:javascript
复制
FDate_Time.Modified := (FDate_Time.Modified) or
           (not SameDateTime(FDate_Time.FieldValue, 
            FCalendarEdit.Date + FTimeEdit.Time));
票数 5
EN

Stack Overflow用户

发布于 2015-10-07 01:09:02

TCalendarEdit中有一个bug (实际上有几个),这是您的问题的根本原因,但是您只需要对代码进行小的更改就可以修复它。

问题

TCalendarEdit在应用新的日期值时会产生许多关键错误。

TDate类型实际上只是一个普通的TDateTime,其中您应该忽略时间部分。类似地,TTime是一个TDateTime,您应该忽略日期部分。

但是,您必须在代码中正确地使用这些类型--没有什么可以神奇地使TTime忽略日期或TDate忽略时间。

例如,如果检查TCalendarEdit的构造函数,您将看到它将内部日期/时间初始化为当前系统的日期和时间,但截断它以消除time元素:

代码语言:javascript
复制
Date := Trunc(Now);

到目前一切尚好。

但是,当您通过Date属性应用新值时,它执行以下操作(简化):

代码语言:javascript
复制
if Date <> Value then
  FDateTime := Value + Time;

这两行代码都包含严重的bug:

  1. 它将 Date (返回控件日期值的属性)与分配的进行比较--包括该日期/时间中的任何时间值。相反,它应该只比较的日期部分。
  2. 当将新值分配给内部日期/时间时,它会将时间添加到指定的中。

第一个bug导致对内部属性进行不必要的更改,但在其他方面相对无害。然而,第二个bug要严重得多,并且是导致您的问题的原因。

我假定控件的作者的意图是保持内部日期/时间值的时间部分不变。但是,没有被截断,因此它保留了分配给属性中指定的时间值。更糟糕的是,该控件上没有 time 属性,因此这实际上将当前系统时间添加到中指定的任何时间。

这如何影响您的代码和测试用例

因为您的测试用例涉及中午时间-- 12小时--结果是当您在下午运行此代码时,您的TCalendarEditDate实际上被设置为25-9.2015+ 12小时+控件初始化的时间。

如果您在早上运行代码,它似乎工作,因为时间增加的结果是一个值仍然是在9月25日。

但是,当您在下午运行代码时,将12个小时添加到当前时间,因此日期将在上滚动到第二天

使用更有用的诊断错误消息,或者如果您通过调试器检查了代码中的属性,就会看到这种情况发生。

代码语言:javascript
复制
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

因此,您的比较失败的原因是因为日期实际上是完全不同的!

如果您尝试简单地使用SameDateTime()进行比较,那么如果您在早上测试它,它可能已经成功了,但是您的问题会在下午再次出现!!

解决方案

您可以通过确保自己尊重属性值的预期使用来解决TCalendarEdit中的这些bug,只在每种情况下酌情分配DT日期/时间值的这些部分:

代码语言:javascript
复制
TimeEdit1.Time     := TimeOf(DT);
CalendarEdit1.Date := DateOf(DT);

虽然对于TCalendarEdit中的TTimeEdit来说,这并不是绝对必要的,但这将防止这些bug导致这些问题,并在代码中清楚地表明您知道需要什么(如果您愿意的话,可以考虑自己记录代码)。:)

如果在您的Delphi版本中没有TimeOf()DateOf()函数,那么以下内容是等效的:

代码语言:javascript
复制
TimeEdit1.Time     := DT - Trunc(DT);
CalendarEdit1.Date := Trunc(DT);

当然,您可以在此基础上编写自己版本的TimeOf()DateOf(),以使意图更加清晰。

备注

Delphi中日期/时间值的浮点性质可能会导致与日期和时间的某些特定值的直接比较出现问题,因此,强烈建议您使用SameDateTime()函数来执行这种比较。

但在这种情况下,这绝对是,而不是,是问题的原因,而做的不是,而是解决您的问题。

SameDateTime()消除了因日期/时间值小于1毫秒而产生的问题。这个案子的差别是24小时!

值得注意的是,TCalendarEdit控件在XE7中不受欢迎,并且已经从XE8中完全删除。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/32975902

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档