如何计算两个TDateTime的相隔时间(年、月、日、时、分、秒)? (100分)

  • 主题发起人 叮叮当当
  • 开始时间
ShowMessage(formatdatetime('yyyy-mm-dd hh:ss:nn:zzz', T2-T1-693594));

返回字符串: 0001-01-02 18:48:28:000
通过分析字符串的“-”“:”得到你要的各个值,再转为整数
(则把当中0开头的值的0去之)
 
忘了说明,分析的结果中,日期的各部分的值均减1[:)]
 
呵呵,萧兄高明,能否讲讲693594的含义,谢谢。

萧兄的程序好象还有点小问题,对跨月处理、闰年处理都有点问题:
小弟的试验:
procedure TForm1.Button1Click(Sender: TObject);
var
t1,t2:TDateTime;
i:Integer;
s,s1:String;
begin

T1:=StrToDateTime('2003-12-01 12:30:00');

for i := 0 to 370 do
begin
T2 := T1 + i;
s:=formatdatetime('yyyy-mm-dd hh:ss:nn:zzz', T2-T1-693595);
s1:=De2DateTime(T1,T2);
memo1.Lines.Add(IntToStr(i)+' : '+DateToStr(t2)+' '+s+' '+s1);
end;
end;
起始:2001-01-29 12:30:00
0 : 2001-1-29 0000-00-00 00:00:00:000 相差 0 年 0 月 0 日 0 小时 0 分 0 秒 0 毫秒 //不用减
...
59 : 2001-3-29 0001-02-27 00:00:00:000 相差 0 年 2 月 0 日 0 小时 0 分 0 秒 0 毫秒 //不整月时 应该进位。
如果是闰年:2004-01-29 12:30:00,则:
31 : 2004-2-29 0001-01-30 00:00:00:000 相差 0 年 1 月 0 日 0 小时 0 分 0 秒 0 毫秒 //整月,进位
60 : 2004-3-29 0001-02-28 00:00:00:000 相差 0 年 2 月 0 日 0 小时 0 分 0 秒 0 毫秒 //整月,进位
如果跨年时:2001-12-1开始
0 : 2001-12-1 0000-00-00 00:00:00:000 相差 0 年 0 月 0 日 0 小时 0 分 0 秒 0 毫秒 //正确
1 : 2001-12-2 0000-00-00 00:00:00:000 相差 0 年 0 月 1 日 0 小时 0 分 0 秒 0 毫秒 //不对
如果时差超过一年时:以2001-12-1开始:
365 : 2002-12-1 0001-12-30 00:00:00:000 相差 1 年 0 月 0 日 0 小时 0 分 0 秒 0 毫秒
366 : 2002-12-2 0001-12-31 00:00:00:000 相差 1 年 0 月 1 日 0 小时 0 分 0 秒 0 毫秒
也有问题。
还请再试试。



 
to 杜宝:
刚刚我验证了一下,那方法对日期部分的处理是可以的,不管跨月处理、闰年处理都是
可以的,但。。。对时间部分处理是错误的,和formatdatetime函数处理结果有关,
搞了半天还是解决不了,看来还是得放弃用formatdatetime的方法了[:(]
 
写成这个过程,日期部分返回结果试了几下,应该是正确的
但时间部分不对,搞不懂 [:(]
procedure GetDateTimeDiff(const D1, D2: TDateTime; var AYear,
AMonth, ADay, AHour, AMin, ASec, AMSec: Word);
var
sDateDiff: string;
begin
//
sDateDiff := formatdatetime('yyyy-mm-dd hh:ss:nn:zzz', D1 - D2 -693594 + 1 );

AYear := StrToInt(Copy(sDateDiff,1,4)) - 1;
AMonth := StrToInt(Copy(sDateDiff,6,2)) - 1;
ADay := StrToInt(Copy(sDateDiff,9,2)) - 1;
AHour := StrToInt(Copy(sDateDiff,11,2));
AMin := StrToInt(Copy(sDateDiff,15,2));
ASec := StrToInt(Copy(sDateDiff,18,2));
AMSec := StrToInt(Copy(sDateDiff,21,3));
end;
 
有个地方写错了,应该从12开始,不是11
AHour := StrToInt(Copy(sDateDiff,12,2));
这样的话,一直到小时部分的差值是正常的
但,毫秒的差值不起作用,这也罢了
最搞不懂的是,分和秒的差值居然是正好相反的
即分差的值出现在秒差的位置,秒差的值出现在分差的位置
顺序似乎又没错。。。。真搞不懂
 
原来是formatdatetime里面的格式写错了(顺手拷上面某仁兄的)
现在可以了,但对毫秒还是不起作用
procedure GetDateTimeDiff(const D1, D2: TDateTime; var AYear,
AMonth, ADay, AHour, AMin, ASec, AMSec: Word);
var
sDateDiff: string;
begin
//
sDateDiff := formatdatetime('yyyy-mm-dd hh:mm:ss:nnn', D1 - D2 -693594 + 1 );

AYear := StrToInt(Copy(sDateDiff,1,4)) - 1;
AMonth := StrToInt(Copy(sDateDiff,6,2)) - 1;
ADay := StrToInt(Copy(sDateDiff,9,2)) - 1;
AHour := StrToInt(Copy(sDateDiff,12,2));
AMin := StrToInt(Copy(sDateDiff,15,2));
ASec := StrToInt(Copy(sDateDiff,18,2));
AMSec := StrToInt(Copy(sDateDiff,21,3));
end;
 
呵呵,小弟以为,萧兄的方法,关键是那个常数定位的准确性。

但有个问题是:日期是有一定连续性的,是线性的;但读法就不一定,它是非线性的
因此如果用一个常数来处理的话(等差数列,是线性的),我想是有问题的。
还有就是小数的问题,如果用常数的话,涉及到与整数位的进位,这样就有问题了。

萧兄的第一次修改程序小弟测试还是有问题:
比如:
2001.12.1与 2002-10-31 0 小时 0 分 0 秒
萧兄的是:
0 年 11 月 0 日
小弟的是:
0 年 10 月 30 日 0 小时 0 分 0 秒 0 毫秒,

第二次的formatDateTime修改后,小数位先不论:

2001.12.1 0:0:0.000

364 : 2002-11-30
相差 0 年 11 月 30 日 0 小时 0 分 0 秒 0 毫秒 //萧兄
相差 0 年 11 月 29 日 0 小时 0 分 0 秒 0 毫秒 //小弟

加上小数位后差异更大。

想请萧兄把测试程序贴出来。

 
这是我的测试程序,通过修改edit中的日期格式的字符串的值来进行测试
不是很全面,不过我试了许多例子确实没发现错误的结果
另外,至于那个常数,你用0减去它,再转为日期型,看看是什么[:)]
procedure TForm1.rbbtnRzBitBtn1Click(Sender: TObject);
var
AYear, AMonth, ADay, AHour, AMin, ASec, AMSec: Word;
D1, D2: TDateTime;
begin
D1 := StrToDateTime(edit2.text);//2002-01-01 00:00:00:000 格式
D2 := StrToDateTime(edit3.text);//2002-01-01 00:00:00:000

GetDateTimeDiff(D1, D2, AYear, AMonth, ADay, AHour, AMin, ASec, AMSec);

edit1.text := inttostr(AYear)+ '年'+ inttostr(AMonth)+'月'+ inttostr(ADay)+'日'+
inttostr(AHour)+'小时'+ inttostr(AMin)+'分'+ inttostr(ASec)+'秒';

end;
 
萧兄的东东,小弟把它改了一下:
function GetDateTimeDiff(const D1, D2: TDateTime):String;
const
Fmt = '相差 %d 年 %d 月 %d 日 %d 小时 %d 分 %d 秒 %d 毫秒';

var
sDateDiff: string;
AYear, AMonth, ADay, AHour, AMin, ASec, AMSec: Word;
begin
//

sDateDiff := formatdatetime('yyyy-mm-dd hh:mm:ss:nnn', D2 - D1 -693594 +1 );

AYear := StrToInt(Copy(sDateDiff,1,4)) - 1;
AMonth := StrToInt(Copy(sDateDiff,6,2)) - 1;
ADay := StrToInt(Copy(sDateDiff,9,2)) - 1;
AHour := StrToInt(Copy(sDateDiff,12,2));
AMin := StrToInt(Copy(sDateDiff,15,2));
ASec := StrToInt(Copy(sDateDiff,18,2));
AMSec := StrToInt(Copy(sDateDiff,21,3));
Result :=inttostr(AYear)+ '年'+ inttostr(AMonth)+'月'+ inttostr(ADay)+'日'+
inttostr(AHour)+'小时'+ inttostr(AMin)+'分'+ inttostr(ASec)+'秒';

// Result := Format(Fmt,[AYear,AMonth,ADay,AHour,AMin,ASec,AMSec]);
end;

我的测试代码:
procedure TForm1.Button1Click(Sender: TObject);
var
t1,t2:TDateTime;
i:Integer;
s,s1:String;
begin

T1:=StrToDateTime('2003-12-01 0:0:0.000');

for i := 0 to 370 do
begin
T2 := T1 + i*1{.01}; //测试小数不注释。
s:=GetDateTimeDiff(t1,t2);
s1:=De2DateTime(T1,T2);
memo1.Lines.Add(IntToStr(i)+' : '+DateTimeToStr(t2)+' '+s+' '+s1);
end;
end;

在小弟的机器上还是有问题:
365 : 2004-11-30 1年0月0日0小时0分0秒 相差 0 年 11 月 29 日 0 小时 0 分 0 秒 0 毫秒
366 : 2004-12-1 1年0月1日0小时0分0秒 相差 1 年 0 月 0 日 0 小时 0 分 0 秒 0 毫秒
367 : 2004-12-2 1年0月2日0小时0分0秒 相差 1 年 0 月 1 日 0 小时 0 分 0 秒 0 毫秒

我的想法,萧兄不应该把这个常数设定为整数,可能它是一个小数的说。
我想这和数据类型的二进制表示精度有关。
但现在我对这种方法,不太看好,原因就是上面我说的。^_^
 
杜大侠果然是个严谨的人[:)]
不过那个常数是经过换算得来的,的确是个整数,加上小数反而不正确了
其实我只想尽量用最简单的方法去解决问题
我再测试一下,如果真的不行再考虑放弃罢
 
个人认为时间相差的年跟月很难说,倒不如说过了多少天来的准确;所以日期部分自己去
改良一下(不知道我的结果正确不正确,反正我没有测试。呵呵!)。
function GetDateTimeDiff(const D1, D2: TDateTime): String;
const
Fmt = '现已经正常运行 %d 年 %d 月 %d 日 %d 小时 %d 分 %d 秒';
var
DT : TDateTime;
D : Integer;
T : TTime;
Year, Month, Day : Word;
Hour, Min, Sec, mSec, mSec0, mSec1 : Word;
begin
DT := D2 - D1;
D := Trunc(DT);
T := DT - D;
if D > 0 then
begin
Year := D div 365;
Month := (D mod 365) div 30;
Day := (D mod 365) mod 30;
end
else
begin
Year := 0;
Month := 0;
Day := 0;
end;
DecodeTime(D2, Hour, Min, Sec, mSec0);
DecodeTime(D1, Hour, Min, Sec, mSec1);
if mSec1 > mSec0 then
T := T + EncodeTime(0, 0, 1, 0);
DecodeTime(T, Hour, Min, Sec, mSec);
Result := Format(Fmt, [Year, Month, Day, Hour, Min, Sec]);
end;
 
各位的讨论好精辟呀,收藏。
 
呵呵,首先,我的程序有问题,就是对于1.31 到 3.1之后的日期取数有问题:
2001.1.31 到 2001.3.1 相隔应该是1个月零1天,但我的上面的程序是30天,
这是不对的。
原因是对天数的取值不对,因为天数与月分的关系不是单纯的进位的关系,所
以不能直接借位取。改成:
...
DDay := EDay - SDay;//取天差,这里就复杂了!
if DDay < 0 then
begin
DDay := DaysInMonth(SDay) - SDay + EDay;//到月底,再加
EDate := IncMonth(EDate,-1);
InitVars;
end;
...
就OK 了。

萧兄的说常数的问题,是我的概念有问题,萧兄是对的。我试了一下,萧兄的方法对于秒
位的处理都是正确的。微秒的方法应该是精度的问题了。


黎兄的程序对于时间的处理是对的,但对日期的处理还是有问题。除了我说2月计算以外
对于跨月的处理有问题例如:
从2004.1.31到 2005-1-25
现已经正常运行 0 年 12 月 0 日 0 小时 0 分 0 秒
相差 0 年 11 月 25 日 0 小时 0 分 0 秒 0 毫秒
出现了12个月的情况。这不是进位的问题是计算不对。
 
呵呵,楼上说的TTimeStamp,不太明白的说,还请举例说明。

早晨心情好,重写了一个,应该比较好了,上面的例子都通过了,你再试试。
function De2DateTime(Const St,Et :TDateTime):String;
const
Fmt = '相差 %d 年 %d 月 %d 日 %d 小时 %d 分 %d 秒 %d 毫秒';
var
//以下变量,S开始的为开始时间,E开始的为结束时间。
SYear,EYear,
SMonth,EMonth,
SDay,EDay:Word;
SHour,EHour,
SMin,EMin,
SSec,ESec,
SMSec,EMSec:Word;

EDate:TDateTime;//起、至数据,只是传入参数不是Var的,做中间变量用。

DYear,DMonth,DDay,DHour,DMin,DSec,DMSec:Integer;//时差。
I:Integer;//中间变量,记录时差间隔。

//中间过程,初始变量。
procedure InitVars;
begin
DeCodeDate(EDate,EYear,EMonth,EDay);
DeCodeTime(EDate,EHour,EMin,ESec,EMSec);
end;
begin
if St > Et then //参数出错。
begin
Result := 'Error!';
Exit;
end;
//变量初始化。
// DYear:=0 ;DMonth:=0;DDay:=0;DHour:=0;DMin:=0;DSec:=0;DMSec:=0;
EDate := Et;
DeCodeDate(St,SYear,SMonth,SDay);
DeCodeTime(St,SHour,SMin,SSec,SMSec);

InitVars;
//进位的加减。从最低位开始!
//先来毫秒位:

DMSec := EMSec - SMSec;//取毫秒位差
if DMSec < 0 then //不足应该借位
begin
DMSec := 1000 + DMSec;
EDate := IncSecond(EDate,-1);//借位。
InitVars;//改变量。
end;
DSec := ESec - SSec ;//取秒位差
if DSec < 0 then
begin
DSec := 60 + DSec;
EDate := IncMinute(EDate,-1);
InitVars;
end;
DMin := EMin - SMin;//取分钟差
if DMin < 0 then
begin
DMin := 60 + DMin;
EDate := IncHour(EDate,-1);
InitVars;
end;

DHour := EHour - SHour;//取小时差
if DHour < 0 then
begin
DHour := 24 + DHour;
EDate := IncDay(EDate,-1);
InitVars;
end;
//**********************************************
//改:
DDay := EDay - SDay;//取天差,这里就复杂了!
if DDay < 0 then
begin
DDay := DaysInMonth(SDay) - SDay + EDay;//到月底,再加
EDate := IncMonth(EDate,-1);
InitVars;
end;
{ 原先的是错的
******************************************
DDay := EDay - SDay;//取天差,这里就复杂了!
if DDay < 0 then
begin
while DDay < 0 do
begin
i:= DaysBetween(EDate , IncMonth(EDate,-1));//取当月天数
DDay := i + DDay;
EDate :=IncMonth(EDate,-1);
end;
InitVars;
end;
***************************************
}
DMonth := EMonth - SMonth ;
if DMonth < 0 then
begin
DMonth := 12 + DMonth;
EDate := IncYear(EDate,-1);
InitVars;
end;

DYear := EYear - SYear;
if DYear < 0 then
begin
Result := 'Error!';
Exit;
end;
//结果!
Result := Format(Fmt,[DYear,DMonth,DDay,DHour,DMin,DSec,DMSec]);

end;

procedure TForm1.Button1Click(Sender: TObject);
var
t1,t2:TDateTime;
begin

T1:=StrToDateTime('2002-01-31 20:59:59.540');

T2:=StrToDateTime('2002-2-11 19:00:00.001');

ShowMessage(De2DateTime(T1,T2));

end;


 
To : 杜宝
>> 黎兄的程序对于时间的处理是对的,但对日期的处理还是有问题。
呵呵,我可能没有说清楚我的意思;我说了,时间相差的年跟月很难说,
倒不如说过了多少天来的准确(所以我算了天数后随便处理年月日的关系);
我在日期部分的处理只是起个抛砖引玉的作用,并不是有问题。
所以我说明了自己去改良后再用啊。什么都说的清清楚楚的就没有意思了。
 
呵呵,黎兄所说的道理,小弟是极为认同的。本贴的问题其实是个惯用语的问题。
惯用语确实不如时间差来得精确。但楼主就是这么要求的,小弟也没办法^_^

其实我与萧兄讨论的重点, 是在于能不能用简单的增减一个常量来读出这个惯用语
现在看来,常数的方法可能有问题,2月份的处理这关就过不了。所以我现在怀疑黎
兄的东东不能能改良得出来。

就事论事,黎兄见笑了。



 
如果今天晚上睡觉时还没有结束就抽空写完整一点。希望杜兄和萧兄写一个
简单有效的过程函数来解决,呵呵。我觉得这个问题虽然不难,但要有效并简单
解决就。。。,不知道大家怎么看?
 
To: 杜宝
你早上重写的程序好像没问题了,我测试了几个数据,很完美!:)[:)]
我正在消化吸收,如果没有意外的话,这个帖子就快结了。[8D]
 
凭良心说,我那个函数有什么问题吗?
我真的测不出不妥的地方呀???[:(]
 
顶部