2010-11-08 45 views
13

在Delphi中是否存在一個將TDateTime值舍入爲最接近的秒,最近的小時,最近的5分鐘,最近的半小時等的例程?德爾福:我如何將TDateTime四捨五入到最接近的秒,分鐘,五分鐘等?

更新:

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; 
+0

我的答案有什麼問題? – 2010-11-08 23:07:13

+0

沒什麼,真的,我只是碰巧先測試Gabr的解決方案。另外,他對間隔種類和大小的單個參數的建議比具有兩個參數的同一事物的解決方案更加優雅。至少在我看來。 – 2010-11-09 06:40:55

+0

這是一個非常有用的代碼,我發現日期時間傾向於「漂移」,如果你增加幾小時或幾分鐘多次。如果你在嚴格的時間序列上工作,這可能會搞砸了。關於你的例子,雖然Svein有些小問題,默認值不適合我,還有退出後的'(vTimeSec/SecsPerDay)',我認爲這是一個錯誤,它不應該在那裏。我帶有更正和評論的代碼是: – SolarBrian 2012-11-12 20:50:34

回答

8

類似的東西(完全未經測試,直接在瀏覽器中編寫):

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; 

該代碼假定您希望以二次精度舍入。毫秒被扔掉。

+0

謝謝!有一些小錯誤,但我把它清理了一下:-) – 2010-11-08 17:12:46

2

這是未經測試的代碼,精度可調。

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; 
2

嘗試使用DateUtils單位。
但是要在一分鐘,幾小時甚至幾秒鐘內舍入,只需解碼,然後將日期值編碼,並將毫秒,秒和分鐘設置爲零。舍入到幾分鐘或幾小時就意味着:解碼,將小時或分鐘舍入或舍入,然後重新編碼。
要對時間值進行編碼/解碼,請使用SysUtils的EncodeTime/DecodeTime。對日期使用EncodeDate/DecodeDate。應該可以用所有這些創建你自己的舍入函數。
此外,SysUtils函數具有常量,如MSecsPerDay,SecsPerDay,SecsPerMin,MinsPerHour和HoursPerDay。時間基本上是午夜過後的毫秒數。您可以使用MSecsPerDay進行壓縮壓縮(時間),這是精確的毫秒數。
不幸的是,由於時間值是​​浮動的,總會有小的舍入誤差的機會,因此你可能得不到預期的值...

7

哇!夥計們,你們怎麼把這麼簡單的事情搞得太複雜了......你們大多數人都沒有選擇輪到最接近的1/100秒,等等......

這一個更簡單,也可以輪到milisenconds部分:

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秒。

5

如果你想綜述或ROUNDDOWN ...喜歡的Ceil和地板......

這裏有(不要忘記數學單元添加到您的使用條款):

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; 

而且當然也可以用一個小的改變(使用浮點類型而不是TDateTime類型),如果也可以用來將Round,RoundUp和RoundDown的十進制/浮點值轉換爲十進制/浮點數。

在這裏,他們是:

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; 
0

這是一個非常有用的代碼,我使用這個,因爲我發現日期時間傾向於「漂移」,如果你將它增加數小時或數分鐘多次,如果你正在嚴格的時間序列工作,這可能會搞砸了。例如00:00:00.000變成23:59:59.998 我實現了Gaben代碼的Sveins版本,但是我建議做一些修改:默認值對我來說不起作用,也是'(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; 
相關問題