Delphi显示TDateTime为负值的操作的奇怪结果

Delphi显示TDateTime为负值的操作的奇怪结果,datetime,delphi,Datetime,Delphi,我们在Delphi中有一个解决方案,可以计算给定车辆的行程持续时间,例如20分钟、25分钟等等。但是,有时我们必须提前预约旅行的开始时间,从特定的日期时间开始,例如09:00到08:40。然后,我们需要从TDateTime变量(travel’s start)中减去一个负值,在本例中,类似于“-00:20”。为此,我们将datetime值乘以-1(例如MyDiffDateTimeVariable*-1)。我们得到的输出非常奇怪,有时我们得到完全相反的行为。在另一种情况下,提取20分钟的操作会导致与

我们在Delphi中有一个解决方案,可以计算给定车辆的行程持续时间,例如20分钟、25分钟等等。但是,有时我们必须提前预约旅行的开始时间,从特定的日期时间开始,例如09:00到08:40。然后,我们需要从TDateTime变量(travel’s start)中减去一个负值,在本例中,类似于“-00:20”。为此,我们将datetime值乘以-1(例如MyDiffDateTimeVariable*-1)。我们得到的输出非常奇怪,有时我们得到完全相反的行为。在另一种情况下,提取20分钟的操作会导致与原始日期时间相差两天

下面是一个示例控制台应用程序,它模拟了我们的情况、当前输出以及我们预期的结果:

program DateTimeSample;

uses
  System.SysUtils, System.DateUtils;

var
  LDate1: TDateTime;
  LDate2: TDateTime;
begin
  LDate1 := IncMinute(0, 20);
  LDate2 := IncMinute(0, -20);
  WriteLn('Date1: ' + DateTimeToStr(LDate1));
  // Output = Date1: 30/12/1899 00:20:00 [OK]
  WriteLn('Date2: ' + DateTimeToStr(LDate2));
  // Output = Date2: 29/12/1899 23:40:00 [OK]
  WriteLn('-----');
  WriteLn('Date1: ' + DateTimeToStr(LDate1 * -1));
  // Output = Date1: 30/12/1899 00:20:00 [Expected 29/12/1899 23:40:00]
  WriteLn('Date2: ' + DateTimeToStr(LDate2 * -1));
  // Output = Date2: 31/12/1899 23:40:00 [Expected 30/12/1899 00:20:00]
  ReadLn;
end.

编译器在对TDateTime执行数字运算时,似乎总是将其视为正数。试试这个:

uses
  System.SysUtils, System.DateUtils;

function InvertDate(ADateTime: TDateTime): TDateTime;
var
  LMsec: Int64;
begin
  LMsec := MillisecondsBetween(ADateTime, 0); //Always Positive
  if ADateTime > 0 then
    LMsec := 0 - LMsec;
  Result := IncMillisecond(0, LMsec);
end;

var
  LDate1: TDateTime;
  LDate1Negative: TDateTime;
  LDate2: TDateTime;

begin
  try
    LDate1 := IncMinute(0, 20);
    LDate2 := IncMinute(0, -20);
    WriteLn('Date1: ' + DateTimeToStr(LDate1));
    // Output = Date1: 30/12/1899 00:20:00 [OK]
    WriteLn('Date2: ' + DateTimeToStr(LDate2));
    // Output = Date2: 29/12/1899 23:40:00 [OK]
    WriteLn('-----');

    WriteLn('Date1: ' + DateTimeToStr( InvertDate(LDate1) ));
    // Output = Date1: Expected 29/12/1899 23:40:00
    WriteLn('Date2: ' + DateTimeToStr( InvertDate(LDate2) ));
    // Output = Date2: 30/12/1899 00:20:00
    ReadLn;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

当检查转换为double的值时,可以看到: 双精度(LDate1)=0.013888889 双(LDate2)=-1.9861111111111

对我来说似乎是个bug,因为今天它又回来了: 双精度(LDate1)=43168013888889 双精度(LDate2)=43167986111111

编辑:嗯,根据,这不是一个bug,这是一个特性:-)

使用负值
TDateTime
值时,计算必须单独处理时间部分。分数部分反映了一天24小时的分数,不考虑
TDateTime
值的符号。例如,1899年12月29日上午6:00是–1.25,而不是–1+0.25,等于–0.75。从–1到0之间没有TDateTime值

解释发生了什么。基本上,
TDateTime
表示为一个
Double
,但这并不意味着您可以像通常使用
Double
值一样使用它。它的内部结构带有特殊的语义,如果你不能正确处理它们,你肯定会得到一些特殊的行为

您所犯的关键错误是使用日期时间值的负数。这个概念没有真正意义。即使你看一下公元前几年的日期也不行,因为历法系统多年来已经改变了很多次

这是您应该支持处理内部结构细微差别的库例程(无论您的平台是什么)的主要原因。在Delphi中,这意味着您应该使用
SysUtils
DateUtils
例程来处理日期和时间

您似乎试图将持续时间保持为
TDateTime
值。您最好确定首选的度量单位,并使用
Integer
(可能是
Int64
)或
Double
(如果您需要对单位分数的支持)。然后,您可以添加或减去开始或结束时间的持续时间,最好使用库例程

下面的代码演示了一些示例

var
  LStartTime, LEndTime: TDateTime;
  LDuration_Mins: Integer;
begin
  { Init sample values for each calculation }
  LStartTime := EncodeDateTime(2018, 3, 9, 8, 40, 0, 0);
  LEndTime := EncodeDateTime(2018, 3, 9, 9, 0, 0, 0);
  LDuration_Mins := 20;

  { Output result of each calculation }
  Writeln(Format('Whole Duration: %d', [MinutesBetween(LStartTime, LEndTime)]));
  Writeln(Format('Frac Duration: %.6f', [MinuteSpan(LStartTime, LEndTime)]));
  Writeln(Format('Start Time: %s', [FormatDateTime('yyyy-mm-dd hh:nn:ss', IncMinute(LEndTime, -LDuration_Mins))]));
  Writeln(Format('End Time: %s', [FormatDateTime('yyyy-mm-dd hh:nn:ss', IncMinute(LStartTime, LDuration_Mins))]));
end;

其他考虑事项 你说你在处理车辆行驶时间。如果你在处理长途旅行,你可能会考虑其他一些事情

  • 夏令时:如果车辆在DST更改前不久开始行驶,并在DST更改后结束,则在计算缺失值时需要考虑这一点。也许最简单的方法是将日期时间值转换为UTC进行计算。这导致
  • 时区更改:同样,除非您的代码意识到时区,否则您肯定会出错

来自帮助:“TDateTime也支持负值。负值光谱完全反映正值。因此,Now(正值)的值与其负值--Now完全相同。应小心使用负值TDateTime。不正确使用负值可能会导致各种问题。”不要在
TDateTime
数字模式下操作。使用SysUtils和DateUtils助手函数。Delphi版本在这里很重要。在D2009上:
IncMinute(0,20)
的计算结果为
-0.013889
(双精度),这是一种特殊情况,但会生成正确的日期时间值
1899-12-30 00:20:00
(可能是因为“没有
TDateTime
值从-1到0”,所以时间部分仅应用于零日期)。另一方面,
IncMinute(0,-20)
的计算结果为
0.013889
(作为双精度),这会产生日期时间值
1899-12-30 00:20:00
,这完全是错误的。由于D2009中的上述问题:
IncDay(IncMinute(EncodeDate(1899,12,30),20))
计算结果为
1899-12-30 23:40:00
,而实际上它应该是
1899-12-31 00:20:00
@LURD!我曾经认为至少
Date+NumberOfDays
是安全的(在DateUtils.pas存在之前的习惯)。但是,幸运的是,我没有处理1899-12-30之前的日期,因为规则“没有从-1到0的TDateTime值”意味着我的假设可能是由一个错误引起的。