如何避免TDateTime数据舍入

时间:2015-10-06 17:23:41

标签: delphi datetime firemonkey

我正在编写FMX def reverse(stack): tmp1 = Stack() while not stack.is_empty(): tmp1.push(stack.pop()) tmp2 = Stack() while not tmp1.is_empty(): tmp2.push(tmp1.pop()) while not tmp2.is_empty(): stack.push(tmp2.pop()) 的列和单元格类,每个单元格中都包含TGridTCalendarEdit个实例。除了正确处理这些子控件中所做的更改外,一切正常。

TTimeEdit

我们的想法是通过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标志。问题是即使没有任何用户活动,有时也会将此标志设置为true。我怀疑这是由于TDateTime的时间部分的缩小。代码没有其他方式为ModifiedFCalendarEdit.Date分配值。

如何正确比较FTimeEdit.TimeFCalendarEdit.Date中存储的数据与FTimeEdit.Time中存储的数据?

追加

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

FDate_Time.FieldValue

附加2。关于@Ken-White的重要建议。 如果我们用

替换比较线
  FDate_Time.Modified := (FDate_Time.Modified) or
    (DateOf(FDate_Time.FieldValue) <> FCalendarEdit.Date) or
    (TimeOf(FDate_Time.FieldValue)<> FTimeEdit.Time);

工作正常。因此,TDataTime比较必须仅由此函数完成。

2 个答案:

答案 0 :(得分:5)

TDateTime属于type Double,这意味着它是一个浮点值,因此在进行相等比较而不指定可接受的delta(差异)时,会遇到二进制表示的常见问题。

特别是对于TDateTime值,您可以使用DateUtils.SameDateTime将等式比较到不到一毫秒:

FDate_Time.Modified := (FDate_Time.Modified) or
           (not SameDateTime(FDate_Time.FieldValue, 
            FCalendarEdit.Date + FTimeEdit.Time));

答案 1 :(得分:3)

TCalendarEdit (实际上是一些)中存在一个错误,这是导致问题的根本原因,但您只需对代码进行少量更改即可解决此问题。

问题

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

TDate 类型实际上只是普通的 TDateTime ,您应该忽略该时间部分。同样, TTime TDateTime ,您应该忽略日期部分。

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

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

Date := Trunc(Now);

到目前为止一切顺利。

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

if Date <> Value then
  FDateTime := Value + Time;

这些代码行中的两个都包含严重的错误:

  1. 它将日期(返回控件的日期值的属性)与分配的进行比较 - 包括该日期的任何时间值/时间。它应该只比较 date 部分。

  2. 将新值分配到内部日期/时间时,会将时间添加到您指定的

  3. 第一个错误会导致内部属性发生不必要的更改,但相对来说无关紧要。然而,第二个错误要严重得多,这也是造成问题的原因。

    我认为控制作者的意图是保持内部日期/时间值的时间部分不变。但是,不会被截断,因此它会保留在属性赋值中指定的时间值。更糟糕的是,此控件上没有 Time 属性,因此实际上将当前系统时间添加到中指定的任何时间。

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

    由于您的测试用例涉及中午时间--12小时 - 结果是当您在下午运行此代码时, TCalendarEdit 日期实际上是设置为2015年9月25日+ 12小时+ 控件初始化时的时间

    如果您在早上运行代码,它似乎有效,因为添加的时间会产生一个仍然在9月25日的值。

    但是当你在下午运行代码时,12小时会被添加到当前时间,所以日期会滚动到到第二天

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

    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()进行比较,那么如果你在早上上测试它可能已经有效了,但你的问题会在下午!!

    解决方案

    您可以通过确保自己尊重属性值的预期用途,仅分配 DT 日期/时间值的那些部分,来解决 TCalendarEdit 中的这些错误适用于每种情况:

    TimeEdit1.Time     := TimeOf(DT);
    CalendarEdit1.Date := DateOf(DT);
    

    虽然在 TTimeEdit 的情况下并非绝对必要,但这会阻止 TCalendarEdit 中的这些错误导致这些问题,并在您的代码中明确表示您已了解需要什么(如果你愿意,可以考虑自己记录代码)。 :)

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

    TimeEdit1.Time     := DT - Trunc(DT);
    CalendarEdit1.Date := Trunc(DT);
    

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

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

    但在这种情况下,这绝对是问题的原因, SameDateTime() 解决您的问题。

    SameDateTime()消除了因日期/时间值差异小于1毫秒而产生的问题。这种情况的区别是24小时!

    值得注意的是, TCalendarEdit 控件在XE7中已弃用,并已完全从XE8中删除。