Delphi显示TDateTime为负值的操作的奇怪结果
我们在Delphi中有一个解决方案,可以计算给定车辆的行程持续时间,例如20分钟、25分钟等等。但是,有时我们必须提前预约旅行的开始时间,从特定的日期时间开始,例如09:00到08:40。然后,我们需要从TDateTime变量(travel’s start)中减去一个负值,在本例中,类似于“-00:20”。为此,我们将datetime值乘以-1(例如MyDiffDateTimeVariable*-1)。我们得到的输出非常奇怪,有时我们得到完全相反的行为。在另一种情况下,提取20分钟的操作会导致与原始日期时间相差两天 下面是一个示例控制台应用程序,它模拟了我们的情况、当前输出以及我们预期的结果:Delphi显示TDateTime为负值的操作的奇怪结果,datetime,delphi,Datetime,Delphi,我们在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
数字模式下操作。使用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值”意味着我的假设可能是由一个错误引起的。