在Delphi中:如何将TDateTime四舍五入到最接近的秒、分钟、五分钟等?

16

在Delphi中是否存在一种可以将TDateTime值四舍五入到最接近的秒、最接近的小时、最接近的5分钟、最接近的半小时等的例程?

更新:

Gabr提供了一个答案。由于完全缺乏测试,可能会有一些小错误;我稍微整理了一下并进行了测试,以下是最终(?)版本:

function RoundDateTimeToNearestInterval(vTime : TDateTime; vInterval : TDateTime = 5*60/SecsPerDay) : TDateTime;
var
  vTimeSec,vIntSec,vRoundedSec : int64;
begin
  //Rounds to nearest 5-minute by default
  vTimeSec := round(vTime * SecsPerDay);
  vIntSec := round(vInterval * SecsPerDay);

  if vIntSec = 0 then exit(vTimeSec / SecsPerDay);

  vRoundedSec := round(vTimeSec / vIntSec) * vIntSec;

  Result := vRoundedSec / SecsPerDay;
end;

我的答案哪里出了问题? - ali_bahoo
其实,我只是碰巧先测试了Gabr的解决方案。此外,他关于区间类型和大小的单参数建议比使用两个参数的解决方案更加优雅。至少在我看来是这样。 - Svein Bringsli
这是一段非常有用的代码,我发现如果你多次按小时或分钟递增datetime,它往往会“漂移”。如果你在严格的时间序列中工作,这可能会搞乱事情。不过,关于你的示例,我有一些小问题,Svein。默认值对我不起作用,而且退出后的“(vTimeSec / SecsPerDay)”我认为是一个错误,它不应该在那里。我的代码已经进行了更正和注释: - Hamish_Fernsby
8个回答

8

哇!各位,你们是如何把一件如此简单的事情复杂化的...而且你们中的大多数人都失去了四舍五入到最接近1/100秒等的选项...

这个更简单,也可以四舍五入到毫秒部分:

function RoundToNearest(TheDateTime,TheRoundStep:TDateTime):TdateTime;
    begin
         if 0=TheRoundStep
         then begin // If round step is zero there is no round at all
                   RoundToNearest:=TheDateTime;
              end
         else begin // Just round to nearest multiple of TheRoundStep
                   RoundToNearest:=Round(TheDateTime/TheRoundStep)*TheRoundStep;
              end;
    end;

您可以使用以下常见或不太常见的示例进行测试:
// Note: Scroll to bottom to see examples of round to 1/10 of a second, etc

// Round to nearest multiple of one hour and a half (round to 90'=1h30')
ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,156)
                                         ,EncodeTime(1,30,0,0))
                          )
           );

// Round to nearest multiple of one hour and a quarter (round to 75'=1h15')
ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,156)
                                         ,EncodeTime(1,15,0,0))
                          )
           );

// Round to nearest multiple of 60 minutes (round to hours)
ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,156)
                                         ,EncodeTime(1,0,0,0))
                          )
           );

// Round to nearest multiple of 60 seconds (round to minutes)
ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,156)
                                         ,EncodeTime(0,1,0,0))
                          )
           );

// Round to nearest multiple of second (round to seconds)
ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,156)
                                         ,EncodeTime(0,0,1,0))
                          )
           );

// Round to nearest multiple of 1/100 seconds
ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,141)
                                         ,EncodeTime(0,0,0,100))
                          )
           );

// Round to nearest multiple of 1/100 seconds
    ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,156)
                                         ,EncodeTime(0,0,0,100))
                          )
           );

// Round to nearest multiple of 1/10 seconds
    ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,151)
                                         ,EncodeTime(0,0,0,10))
                          )
           );

// Round to nearest multiple of 1/10 seconds
    ShowMessage(FormatDateTime('hh:nn:ss.zzz'
                          ,RoundToNearest(EncodeTime(15,31,37,156)
                                         ,EncodeTime(0,0,0,10))
                          )
           );

希望这能帮到像我一样需要四舍五入至1/100、1/25或1/10秒的人们。

7

类似这样的(完全未经测试,直接在浏览器中编写):

function RoundToNearest(time, interval: TDateTime): TDateTime;
var
  time_sec, int_sec, rounded_sec: int64;
begin
  time_sec := Round(time * SecsPerDay);
  int_sec := Round(interval * SecsPerDay);
  rounded_sec := (time_sec div int_sec) * int_sec;
  if (rounded_sec + int_sec - time_sec) - (time_sec - rounded_sec) then
    rounded_sec := rounded_sec + time_sec;
  Result := rounded_sec / SecsPerDay;
end;

代码假设您想要以秒为精度进行四舍五入。毫秒将被丢弃。

谢谢!有一些小错误,但我稍微修改了一下 :-) - Svein Bringsli
"sec" 变量是什么?它无法编译。 - Paul
应该是time_sec而不是time+sec。已修正,谢谢。 - gabr

6

如果你想要进行向上或向下取整操作,就像Ceil和Floor一样...

这里有一些方法(不要忘记在uses子句中添加Math单元):

function RoundUpToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;
    begin
         if 0=TheRoundStep
         then begin // If round step is zero there is no round at all
                   RoundUpToNearest:=TheDateTime;
              end
         else begin // Just round up to nearest bigger or equal multiple of TheRoundStep
                   RoundUpToNearest:=Ceil(TheDateTime/TheRoundStep)*TheRoundStep;
              end;
    end;

function RoundDownToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;
    begin
         if 0=TheRoundStep
         then begin // If round step is zero there is no round at all
                   RoundDownToNearest:=TheDateTime;
              end
         else begin // Just round down to nearest lower or equal multiple of TheRoundStep
                   RoundDownToNearest:=Floor(TheDateTime/TheRoundStep)*TheRoundStep;
              end;
    end;

当然,只需进行一些小修改(使用Float类型而不是TDateTime类型),它也可以用于将十进制/浮点值四舍五入、向上取整或向下取整到一个十进制/浮点步长。

它们在这里:

function RoundUpToNearest(TheValue,TheRoundStep:Float):Float;
    begin
         if 0=TheRoundStep
         then begin // If round step is zero there is no round at all
                   RoundUpToNearest:=TheValue;
              end
         else begin // Just round up to nearest bigger or equal multiple of TheRoundStep
                   RoundUpToNearest:=Ceil(TheValue/TheRoundStep)*TheRoundStep;
              end;
    end;

function RoundToNearest(TheValue,TheRoundStep:Float):Float;
    begin
         if 0=TheRoundStep
         then begin // If round step is zero there is no round at all
                   RoundToNearest:=TheValue;
              end
         else begin // Just round to nearest multiple of TheRoundStep
                   RoundToNearest:=Floor(TheValue/TheRoundStep)*TheRoundStep;
              end;
    end;

function RoundDownToNearest(TheValue,TheRoundStep:Float):Float;
    begin
         if 0=TheRoundStep
         then begin // If round step is zero there is no round at all
                   RoundDownToNearest:=TheDateTime;
              end
         else begin // Just round down to nearest lower or equal multiple of TheRoundStep
                   RoundDownToNearest:=Floor(TheValue/TheRoundStep)*TheRoundStep;
              end;
    end;

如果您想在同一个单元中使用 TDateTime 和 Float 类型,请在接口部分添加重载指令,例如:
function RoundUpToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;overload;
function RoundToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;overload;
function RoundDownToNearest(TheDateTime,TheRoundStep:TDateTime):TDateTime;overload;

function RoundUpToNearest(TheValue,TheRoundStep:Float):Float;overload;
function RoundToNearest(TheValue,TheRoundStep:Float):Float;overload;
function RoundDownToNearest(TheValue,TheRoundStep:Float):Float;overload;

3

这是一段未经测试的、可调精度的代码。

Type
  TTimeDef = (tdSeconds, tdMinutes, tdHours, tdDays)

function ToClosest( input : TDateTime; TimeDef : TTimeDef ; Range : Integer ) : TDateTime
var 
  Coeff : Double;
RInteger : Integer;
DRInteger : Integer;
begin
  case TimeDef of
    tdSeconds :  Coeff := SecsPerDay;  
    tdMinutes : Coeff := MinsPerDay;
    tdHours : Coeff :=  MinsPerDay/60;
    tdDays : Coeff := 1;
  end;

  RInteger := Trunc(input * Coeff);
  DRInteger := RInteger div Range * Range
  result := DRInteger / Coeff;
  if (RInteger - DRInteger) >= (Range / 2) then
    result := result + Range / Coeff;

end;

3
尝试使用DateUtils单元。但是要将时间舍入到分钟、小时甚至秒,只需解码然后重新编码日期值,毫秒、秒和分钟设置为零。舍入到分钟或小时的倍数只需要:解码,向上或向下舍入小时或分钟,然后再次编码。使用SysUtils中的EncodeTime/DecodeTime对时间值进行编码/解码。对于日期,请使用EncodeDate/DecodeDate。使用所有这些应该可以创建自己的舍入函数。此外,SysUtils函数具有像MSecsPerDay、SecsPerDay、SecsPerMin、MinsPerHour和HoursPerDay之类的常量。时间基本上是午夜后的毫秒数。您可以将Frac(Time)乘以MSecsPerDay,这是确切的毫秒数。不幸的是,由于时间值是浮点数,因此总会存在小的舍入误差的可能性,因此您可能无法获得预期的值...

2

如果有人看到了这篇文章的末尾,那么这里还有一个想法。正如z666zz666z所说,它并不需要很复杂。在Delphi中,TDateTime是一个双精度浮点数,整数部分代表天数。如果将四舍五入值作为一天中的“周期”数传递,则四舍五入函数将简单地为:Round(dt * RoundingValue) / RoundingValue。该方法如下:

procedure RoundTo(var dt: TDateTime; RoundingValue:integer);
    begin
    if RoundingValue > 0 then
        dt := Round(dt * RoundingValue) / RoundingValue;
    end;

示例:

RoundTo(targetDateTime, SecsPerDay); // round to the nearest second
RoundTo(targetDateTime, SecsPerDay div 10); // round to the nearest 10 seconds
RoundTo(targetDateTime, MinsPerDay); // round to the nearest minute
RoundTo(targetDateTime, MinsPerDay div 5); // round to the nearest five minutes
RoundTo(targetDateTime, HoursPerDay); // round to the nearest hour

它甚至支持亚秒舍入:
RoundTo(targetDateTime, SecsPerDay * 10); // round to the nearest 1/10 second

0

这是一段非常有用的代码,我使用它是因为如果你多次增加小时或分钟,日期时间往往会“漂移”,这可能会在你按照严格的时间序列工作时造成混乱。例如,00:00:00.000 变成了 23:59:59.998。 我实现了 Sveins 版本的 Gabrs 代码,但我建议进行一些修改:默认值对我不起作用,另外,在退出后的“(vTimeSec / SecsPerDay)”我认为是一个错误,它不应该在那里。我的代码已经进行了更正和注释:

    Procedure TNumTool.RoundDateTimeToNearestInterval
                        (const ATime:TDateTime; AInterval:TDateTime{=5*60/SecsPerDay}; Var Result:TDateTime);
    var                                            //Rounds to nearest 5-minute by default
      vTimeSec,vIntSec,vRoundedSec : int64;     //NB datetime values are in days since 12/30/1899 as a double
    begin
      if AInterval = 0 then
        AInterval := 5*60/SecsPerDay;                 // no interval given - use default value of 5 minutes
      vTimeSec := round(ATime * SecsPerDay);          // input time in seconds as integer
      vIntSec  := round(AInterval * SecsPerDay);      // interval time in seconds as integer
      if vIntSec = 0 then
        exit;                                           // interval is zero -cannot round the datetime;
      vRoundedSec := round(vTimeSec / vIntSec) * vIntSec;   // rounded time in seconds as integer
      Result      := vRoundedSec / SecsPerDay;              // rounded time in days as tdatetime (double)
    end;

0

最简单的(四舍五入到分钟):

DateTime := OneMinute * Round(DateTime / OneMinute);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接