代码之家  ›  专栏  ›  技术社区  ›  marco-fiset

如何解决Delphi无法准确处理日期时间操作的问题?

  •  13
  • marco-fiset  · 技术社区  · 12 年前

    我是Delphi的新手(已经在里面编程了大约6个月)。到目前为止,这是一次非常令人沮丧的经历,其中大部分来自德尔福在处理日期和时间方面的糟糕表现。也许我觉得这很糟糕,因为我不知道如何正确使用TDate和TTime,我不知道。以下是我现在发生的事情:

    // This shows 570, as expected
    ShowMessage(IntToStr(MinutesBetween(StrToTime('8:00'), StrToTime('17:30'))));
    
    // Here I would expect 630, but instead 629 is displayed. WTF!?
    ShowMessage(IntToStr(MinutesBetween(StrToTime('7:00'), StrToTime('17:30'))));
    

    这不是我使用的确切代码,一切都在变量中,并在另一个上下文中使用,但我认为你可以看到问题所在。为什么这个计算是错误的?我该如何解决这个问题?

    2 回复  |  直到 12 年前
        1
  •  21
  •   Andreas Rejbrand    12 年前

    鉴于

    a := StrToTime('7:00');
    b := StrToTime('17:30');
    
    ShowMessage(FloatToStr(a));
    ShowMessage(FloatToStr(b));
    

    您的代码,使用 MinutesBetween ,有效地做到了这一点:

    ShowMessage(IntToStr(trunc(MinuteSpan(a, b)))); // Gives 629
    

    然而,最好四舍五入:

    ShowMessage(IntToStr(round(MinuteSpan(a, b)))); // Gives 630
    

    浮点值到底是什么?

    ShowMessage(FloatToStr(MinuteSpan(a, b))); // Gives 630
    

    因此,您显然受到了传统浮点问题的困扰。

    更新:

    的主要好处 Round 如果分钟跨度非常接近一个整数,那么四舍五入的值将保证是该整数,而截断的值很可能是前面的整数。

    的主要好处 Trunc 你可能真的想要这样的逻辑:事实上,如果你在五天后年满18岁,从法律上讲,你仍然不能申请瑞典驾照。

    所以,如果你想使用 圆形的 而不是 Trunc公司 ,您可以添加

    function MinutesBetween(const ANow, AThen: TDateTime): Int64;
    begin
      Result := Round(MinuteSpan(ANow, AThen));
    end;
    

    到你的单位。然后是标识符 介于两者之间的分钟数 将指代同一单元中的这个,而不是中的那个 DateUtils 。一般规则是编译器将使用它最新发现的函数。例如,如果你把上面的这个函数放在你自己的单元中 DateUtilsFix 然后

    implementation
    
    uses DateUtils, DateUtilsFix
    

    将使用新的 介于两者之间的分钟数 自从 日期UtilsFix 发生在 日期Utils .

    更新2:

    另一种可行的方法可能是

    function MinutesBetween(const ANow, AThen: TDateTime): Int64;
    var
      spn: double;
    begin
      spn := MinuteSpan(ANow, AThen);
      if SameValue(spn, round(spn)) then
        result := round(spn)
      else
        result := trunc(spn);
    end;
    

    这将返回 round(spn) 是跨度在整数的模糊范围内,并且 trunc(spn) 否则

    例如,使用这种方法

    07:00:00 and 07:00:58
    

    将产生0分钟的时间,与原始时间相同 trunc -基础版本,就像瑞典Trafikwerket想要的一样。但它不会受到引发OP问题的问题的影响。

        2
  •  8
  •   Community CDub    7 年前

    这个问题在Delphi的最新版本中得到了解决。因此,您可以升级,也可以简单地使用Delphi2010中的新代码。例如,此程序生成您期望的输出:

    {$APPTYPE CONSOLE}
    uses
      SysUtils, DateUtils;
    
    function DateTimeToMilliseconds(const ADateTime: TDateTime): Int64;
    var
      LTimeStamp: TTimeStamp;
    begin
      LTimeStamp := DateTimeToTimeStamp(ADateTime);
      Result := LTimeStamp.Date;
      Result := (Result * MSecsPerDay) + LTimeStamp.Time;
    end;
    
    function MinutesBetween(const ANow, AThen: TDateTime): Int64;
    begin
      Result := Abs(DateTimeToMilliseconds(ANow) - DateTimeToMilliseconds(AThen))
        div (MSecsPerSec * SecsPerMin);
    end;
    
    begin
      Writeln(IntToStr(MinutesBetween(StrToTime('7:00'), StrToTime('17:30'))));
      Readln;
    end.
    

    Delphi 2010代码 MinutesBetween 看起来是这样的:

    function SpanOfNowAndThen(const ANow, AThen: TDateTime): TDateTime;
    begin
      if ANow < AThen then
        Result := AThen - ANow
      else
        Result := ANow - AThen;
    end;
    
    function MinuteSpan(const ANow, AThen: TDateTime): Double;
    begin
      Result := MinsPerDay * SpanOfNowAndThen(ANow, AThen);
    end;
    
    function MinutesBetween(const ANow, AThen: TDateTime): Int64;
    begin
      Result := Trunc(MinuteSpan(ANow, AThen));
    end;
    

    所以 介于两者之间的分钟数 有效地归结为两个日期/时间值的浮点减法。由于浮点运算固有的精确性,这种减法可以产生略高于或低于真实值的值。当它低于真实值时,使用 Trunc 会一直带你到前一分钟。简单地更换 Trunc公司 具有 Round 将解决问题。


    正如最新的Delphi版本所发生的那样,彻底修改日期/时间计算。在 DateUtils 。分析起来有点困难,但新版本依赖于 DateTimeToTimeStamp 。它将该值的时间部分转换为自午夜以来的毫秒数。它是这样做的:

    function DateTimeToTimeStamp(DateTime: TDateTime): TTimeStamp;
    var
      LTemp, LTemp2: Int64;
    begin
      LTemp := Round(DateTime * FMSecsPerDay);
      LTemp2 := (LTemp div IMSecsPerDay);
      Result.Date := DateDelta + LTemp2;
      Result.Time := Abs(LTemp) mod IMSecsPerDay;
    end;
    

    注意使用 圆形的 .使用 圆形的 而不是 Trunc公司 是最新Delphi代码处理的原因吗 介于两者之间的分钟数 以稳健的方式。


    假设您现在无法升级,我会处理这样的问题:

    1. 保持代码不变。继续呼叫 介于两者之间的分钟数
    2. 当您进行升级时,您的代码调用 介于两者之间的分钟数 等等现在将起作用。
    3. 在此期间修复 介于两者之间的分钟数 等等 code hooks 。当你真的来升级时,你可以简单地移除挂钩。